EMMA Coverage Report (generated Sun May 02 20:42:29 CEST 2010)
[all classes][hu.netmind.beankeeper.parser]

COVERAGE SUMMARY FOR SOURCE FILE [WhereResolver.java]

nameclass, %method, %block, %line, %
WhereResolver.java100% (2/2)95%  (20/21)82%  (2772/3368)92%  (568.3/618)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class WhereResolver$SymbolTableEntry100% (1/1)50%  (1/2)41%  (19/46)86%  (6/7)
toString (): String 0%   (0/1)0%   (0/27)0%   (0/1)
WhereResolver$SymbolTableEntry (): void 100% (1/1)100% (19/19)100% (6/6)
     
class WhereResolver100% (1/1)100% (19/19)83%  (2753/3322)92%  (562.3/611)
fixLeftTableTerms (SpecifiedTableTerm, WhereResolver$SymbolTableEntry): void 100% (1/1)64%  (106/165)85%  (23/27)
generate (QueryStatement): QueryStatementList 100% (1/1)67%  (308/463)85%  (51.9/61)
fixDateConstraints (Collection, Expression, TimeControl): Expression 100% (1/1)74%  (121/163)88%  (29/33)
resolve (ClassSpecifier, List): ReferenceTerm 100% (1/1)82%  (771/945)93%  (158/170)
fixAtomicExpressions (Object, Object, String): Expression 100% (1/1)83%  (107/129)89%  (24/27)
validateAggregateClauses (QueryStatement): void 100% (1/1)85%  (235/276)89%  (46.3/52)
fixNonstorableTerms (List, Expression): void 100% (1/1)87%  (72/83)88%  (15/17)
fixOrderBys (QueryStatement): void 100% (1/1)87%  (74/85)94%  (15/16)
getReferenceTerm (WhereResolver$SymbolTableEntry): ReferenceTerm 100% (1/1)90%  (18/20)75%  (3/4)
fixThreeValuedLogicExpressions (ReferenceTerm, ReferenceTerm, String): Expres... 100% (1/1)90%  (167/185)97%  (36/37)
fixAutomaticTerms (Expression): Expression 100% (1/1)90%  (168/186)95%  (36/38)
fixPrimitiveExpression (ReferenceTerm, ConstantTerm, String): Expression 100% (1/1)94%  (160/170)97%  (31/32)
validViewSelectTables (QueryStatement): boolean 100% (1/1)95%  (35/37)88%  (7/8)
fixContainsNegated (Expression, boolean): boolean 100% (1/1)97%  (58/60)93%  (14/15)
fixTimeControl (QueryStatement, Set): void 100% (1/1)98%  (80/82)95%  (18/19)
<static initializer> 100% (1/1)100% (4/4)100% (1/1)
WhereResolver (ClassTracker, TypeHandlerTracker, SchemaManager): void 100% (1/1)100% (32/32)100% (11/11)
fixLeftTableTerms (ClassInfo, SpecifiedTableTerm, List): void 100% (1/1)100% (81/81)100% (16/16)
resolve (ClassSpecifier, boolean): TableTerm 100% (1/1)100% (156/156)100% (27/27)

1/**
2 * Copyright (C) 2006 NetMind Consulting Bt.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18 
19package hu.netmind.beankeeper.parser;
20 
21import hu.netmind.beankeeper.model.*;
22import hu.netmind.beankeeper.schema.SchemaManager;
23import hu.netmind.beankeeper.type.TypeHandlerTracker;
24import java.util.*;
25import org.apache.log4j.Logger;
26 
27/**
28 * The parser resolver. Handles resolving objects and attributes
29 * to table names and such. This class also handles the symbol table.
30 * @author Brautigam Robert
31 * @version Revision: $Revision$
32 */
33public class WhereResolver
34{
35   private static Logger logger = Logger.getLogger(WhereResolver.class);
36  
37   private HashMap symbolTable;
38   private SymbolTableEntry lastSymbolTableEntry;
39   // Main class stuff only used by 'find', when selecting single class
40   private SpecifiedTableTerm mainTerm;
41   private ClassInfo mainClassInfo;
42   private ClassSpecifier mainClassSpecifier;
43 
44   private SchemaManager schemaManager = null;
45   private ClassTracker classTracker = null;
46   private TypeHandlerTracker typeHandlerTracker = null;
47 
48   public WhereResolver(ClassTracker classTracker, TypeHandlerTracker typeHandlerTracker,
49         SchemaManager schemaManager)
50   {
51      this.classTracker=classTracker;
52      this.typeHandlerTracker=typeHandlerTracker;
53      this.schemaManager=schemaManager;
54      symbolTable = new HashMap();
55      mainTerm = null;
56      mainClassInfo = null;
57   }
58   
59   /**
60    * Resolve main table name.
61    */
62   public TableTerm resolve(ClassSpecifier specifier, boolean selected)
63   {
64      String alias = specifier.getAlias()!=null?specifier.getAlias():specifier.getClassName();
65      // Insert symbol table entry
66      SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias);
67      if ( entry == null )
68      {
69         // Determine class for specifier
70         ClassInfo classInfo = classTracker.getClassInfo(
71               classTracker.getMatchingClassEntry(specifier.getClassName()));
72         if ( classInfo == null )
73            throw new ParserException(ParserException.SYMBOL_ERROR,"could not find class for table name: "+specifier.getClassName());
74         // Entry was null, create
75         entry = new SymbolTableEntry();
76         entry.specifiedTerm = new SpecifiedTableTerm(
77               schemaManager.getTableName(classInfo.getSourceEntry()), specifier.getAlias());
78         entry.automatic = false;
79         entry.expression = null;
80         entry.selected=true;
81         entry.classInfo = classInfo;
82         symbolTable.put(alias,entry);
83         lastSymbolTableEntry=entry;
84         if ( specifier.getAlias() == null )
85         {
86            // Insert default table name symbol too, for later left table
87            // term fixing, which will use the full table name
88            symbolTable.put(entry.specifiedTerm.getTableName(),entry);
89         }
90         logger.debug("adding table to symbol table: "+entry.specifiedTerm);
91         // Remember main table
92         if ( mainTerm == null )
93         {
94            logger.debug("table entry "+entry.specifiedTerm+" is selected, adding left tables.");
95            // Remember
96            mainClassSpecifier = specifier;
97            mainClassInfo = classInfo;
98            mainTerm = entry.specifiedTerm; // Main term is the selected class/table
99            // Fix entry to contain all relevant classes
100            if ( selected )
101               fixLeftTableTerms(entry.specifiedTerm,entry);
102         }
103      }
104      // Construct table term
105      TableTerm tableTerm = new TableTerm(entry.specifiedTerm);
106      // Return with table term
107      logger.debug("translated: "+specifier+" -> "+tableTerm);
108      return tableTerm;
109   }
110 
111   private ReferenceTerm getReferenceTerm(SymbolTableEntry entry)
112   {
113      switch ( entry.type )
114      {
115         case SymbolTableEntry.TYPE_HANDLED:
116            return new ReferenceTerm(entry.specifiedTerm,entry.referenceColumn);
117         case SymbolTableEntry.TYPE_OBJECT:
118            return new ReferenceTerm(entry.specifiedTerm,"persistence_id");
119         default:
120            return null;
121      }
122   }
123 
124   /**
125    * Resolver attribute chain.
126    */
127   public ReferenceTerm resolve(ClassSpecifier specifier, List attributeSpecifiers)
128   {
129      // Resolve class specifier
130      // This is not as easy as it sounds: if there are no attribute
131      // specifiers and there is no alias given, it may mean that
132      // this identifier represents an attribute to selected main class
133      // (if there is one).
134      if ( ((attributeSpecifiers==null) || (attributeSpecifiers.size()==0)) 
135            && (specifier.getAlias()==null) && (mainClassInfo!=null) )
136      {
137         String attributeName = specifier.getClassName();
138         if ( mainClassInfo.getAttributeType(attributeName) != null )
139         {
140            // Simulate a normal entry
141            specifier = mainClassSpecifier;
142            attributeSpecifiers = new ArrayList();
143            attributeSpecifiers.add(new AttributeSpecifier(attributeName,null,null));
144         }
145      }
146      // Now go through names, the root name does already exist,
147      // because that is the class. All other names are 
148      // combined with attributes (book.title). This also should
149      // be inserted into symbol table, if it is a class.
150      resolve(specifier,false); // Resolve class
151      StringBuffer alias = new StringBuffer(specifier.getAlias()==null?specifier.getClassName():specifier.getAlias());
152      SymbolTableEntry previousEntry = (SymbolTableEntry) symbolTable.get(alias.toString());
153      ClassInfo previousInfo = previousEntry.classInfo;
154      AttributeSpecifier primitiveAttributeSpecifier = null;
155      for ( int i=0; (attributeSpecifiers!=null) && (i<attributeSpecifiers.size()); i++ )
156      {
157         AttributeSpecifier spec = (AttributeSpecifier) attributeSpecifiers.get(i);
158         String attributeName = spec.getIdentifier();
159         Class attributeClass = previousInfo.getAttributeType(attributeName);
160         if ( attributeClass == null )
161            throw new ParserException(ParserException.ABORT,"can not resolve the identifier '"+
162                  attributeName+"' on '"+alias.toString()+"', classinfo: "+previousInfo);
163         alias.append("."+attributeName);
164         logger.debug("processing attribute: "+alias);
165         ClassTracker.ClassType attributeType = classTracker.getType(attributeClass);
166         // Handle additional array specifier.
167         if ( (spec.getKeyname() != null) && (attributeType != ClassTracker.ClassType.TYPE_HANDLED) )
168               throw new ParserException(ParserException.ABORT,"additional array specifier found, but type is not 'handled': "+alias);
169         if ( attributeType == ClassTracker.ClassType.TYPE_HANDLED )
170         {
171            logger.debug("attribute '"+alias+"' found to be a handled type, letting the handler in.");
172            // If the array specifier is given, add to alias
173            if ( spec.getKeyname() != null )
174               alias.append("['"+spec.getKeyname()+"']");
175            // Calculate the symbol table entry for this part
176            SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias.toString());
177            if ( entry == null )
178            {
179               entry = typeHandlerTracker.
180                  getHandler(attributeClass).getSymbolEntry(spec,previousEntry,
181                        previousInfo,getReferenceTerm(previousEntry));
182               if ( entry == null )
183               {
184                  // Entry was not created, this means this is a primitive type
185                  logger.debug("handled attribute '"+alias+"' found to be a primitive attribute.");
186                  // A primitive type should be the last in the list
187                  if ( i+1 < attributeSpecifiers.size() )
188                     throw new ParserException(ParserException.ABORT,"A handled primitive type encountered, but not the last in list: "+alias.toString());
189                  // Mark primitive type
190                  primitiveAttributeSpecifier = spec;
191                  entry = previousEntry; // It's entry is it's parent
192               } else if ( entry.specifiedTerm.getAlias() != null ) {
193                  // Entry wants to be in the symbol table
194                  symbolTable.put(entry.specifiedTerm.getAlias(),entry);
195                  lastSymbolTableEntry=entry;
196               } else {
197                  // Entry does no want into the symbol table,
198                  // so create an extremal symbol.
199                  int num = 0;
200                  while ( symbolTable.containsKey(alias.toString()+"-notused"+num) )
201                     num++;
202                  symbolTable.put(alias.toString()+"-notused"+num,entry);
203                  lastSymbolTableEntry=entry;
204               }
205            }
206            previousEntry = entry;
207            // If this is not the last specifier, then calculate the
208            // class info for the next specifier.
209            if ( i+1 < attributeSpecifiers.size() )
210               previousInfo = typeHandlerTracker.
211                  getHandler(attributeClass).getSymbolInfo(entry,spec);
212         }
213         // Reserved type
214         if ( attributeType == ClassTracker.ClassType.TYPE_RESERVED )
215            throw new ParserException(ParserException.ABORT,"attribute type is reserved, can not handle it: "+alias.toString());
216         // Attribute is an object
217         if ( attributeType == ClassTracker.ClassType.TYPE_OBJECT )
218         {
219            ClassInfo containerInfo = previousInfo;
220            if ( spec.getClassName() != null )
221               previousInfo = classTracker.getClassInfo(
222                     classTracker.getMatchingClassEntry(spec.getClassName()));
223            else
224               previousInfo = classTracker.getClassInfo(previousInfo.getAttributeType(attributeName),null);
225            if ( logger.isDebugEnabled() )
226               logger.debug("attribute '"+alias+"' found to be an object, with entry: "+previousInfo.getSourceEntry());
227            // If the attribute is non-storable, then it will get flagged in
228            // fixNonstorableTerms() method. But here, we let it through, because
229            // maybe, it is compared to a primitive type, in which case, we still
230            // can make a meaningful expression out of it (in fixPrimitiveExpression)
231            if ( ! previousInfo.isStorable() )
232            {
233               ReferenceTerm idTerm = new ReferenceTerm(
234                     previousEntry.specifiedTerm,attributeName);
235               previousEntry.termList.add(idTerm);
236               if ( logger.isDebugEnabled() )
237                  logger.debug("attribute '"+alias+"' refers to a non-storable object, so reference will be: "+idTerm);
238               return idTerm;
239            }
240            // Get entry
241            SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias.toString());
242            if ( entry == null )
243            {
244               // Create entry
245               entry = new SymbolTableEntry();
246               entry.specifiedTerm = new SpecifiedTableTerm(
247                     schemaManager.getTableName(previousInfo.getSourceEntry()),null);
248               entry.automatic = true;
249               entry.type = SymbolTableEntry.TYPE_OBJECT;
250               entry.classInfo = previousInfo;
251               symbolTable.put(alias.toString(),entry);
252               lastSymbolTableEntry=entry;
253               // Create expression
254               Expression expr = new Expression();
255               // Determine whether the entry's tableName is correct, or if
256               // a superclass's table is required for attribute
257               TableTerm previousTerm = new TableTerm(previousEntry.specifiedTerm);
258               ClassEntry superClassEntry = containerInfo.getAttributeClassEntry(attributeName);
259               if ( logger.isDebugEnabled() )
260                  logger.debug("selecting object attribute "+attributeName+", class: "+
261                        previousEntry.classInfo.getSourceEntry()+", superclass: "+superClassEntry);
262               if ( ! previousEntry.classInfo.getSourceEntry().equals(superClassEntry) )
263               {
264                  // Class holding the attribute differs from entry's class.
265                  // Note: if a superclass is in charge, there should be no
266                  // dynamic name.
267                  ClassInfo superInfo = classTracker.getClassInfo(superClassEntry);
268                  String superTableName = schemaManager.getTableName(superClassEntry);
269                  previousTerm = new TableTerm(superTableName,null);
270                  if ( logger.isDebugEnabled() )
271                     logger.debug("determining whether to allocate supertable: "+previousTerm+", tables allocated: "+previousEntry.allocatedSuperTerms);
272                  if ( ! previousEntry.allocatedSuperTerms.contains(previousTerm) )
273                  {
274                     logger.debug("allocating superclass: "+superClassEntry);
275                     // This superclass was not yet allocated, so add
276                     // as a related left term
277                     Expression joinExpr = new Expression();
278                     ReferenceTerm connectLeftTerm = new ReferenceTerm(previousEntry.specifiedTerm,"persistence_id");
279                     previousEntry.termList.add(connectLeftTerm);
280                     joinExpr.add(connectLeftTerm);
281                     joinExpr.add("=");
282                     ReferenceTerm connectRightTerm = new ReferenceTerm(previousTerm,"persistence_id");
283                     previousEntry.termList.add(connectRightTerm);
284                     joinExpr.add(connectRightTerm);
285                     SpecifiedTableTerm.LeftjoinEntry leftEntry =  new SpecifiedTableTerm.LeftjoinEntry();
286                     leftEntry.term=previousTerm;
287                     leftEntry.expression=joinExpr;
288                     previousEntry.specifiedTerm.getRelatedLeftTerms().add(leftEntry);
289                     // Add to list
290                     previousEntry.allocatedSuperTerms.add(previousTerm);
291                  }
292               }
293               // Now join the term to the parent term
294               if ( i+1 >= attributeSpecifiers.size() )
295               {
296                  // Object is the last in the reference list, so it is not
297                  // dereferenced. In this case, we must provide a column
298                  // of persistence_id, which is null if the parent object's
299                  // attribute is null, _or_ if it's not null, but the object
300                  // is deleted. To do this, we only left-join the table of
301                  // the type, so we will get a persistence_id anyway.
302                  ReferenceTerm refTerm = getReferenceTerm(entry);
303                  entry.termList.add(refTerm);
304                  entry.expression=expr;
305                  entry.leftTerm=true; // Mark as not standalone
306                  // Add leftjoin entry
307                  SpecifiedTableTerm.LeftjoinEntry leftEntry = new SpecifiedTableTerm.LeftjoinEntry();
308                  leftEntry.term=refTerm;
309                  Expression joinExpr = new Expression();
310                  leftEntry.expression=joinExpr;
311                  ReferenceTerm toTerm = new ReferenceTerm(previousTerm,attributeName);
312                  previousEntry.termList.add(toTerm);
313                  joinExpr.add(toTerm);
314                  joinExpr.add("=");
315                  ReferenceTerm valueTerm = new ReferenceTerm(refTerm,"persistence_id");
316                  joinExpr.add(valueTerm);
317                  entry.termList.add(valueTerm);
318                  previousEntry.specifiedTerm.getReferencedLeftTerms().add(leftEntry);
319               } else {
320                  // Object is not the last in the reference list, meaning it is
321                  // dereferenced, which in turn means, that it must be hard-joined
322                  // so all the attributes exist, or the select fails if the object
323                  // can not be joined
324                  ReferenceTerm leftTerm = new ReferenceTerm(previousTerm,attributeName);
325                  previousEntry.termList.add(leftTerm);
326                  if ( ! expr.isEmpty() )
327                     expr.add("and");
328                  expr.add(leftTerm);
329                  expr.add("=");
330                  ReferenceTerm refTerm = getReferenceTerm(entry);
331                  expr.add(refTerm);
332                  entry.termList.add(refTerm);
333                  entry.expression=expr;
334               }
335            }
336            previousEntry = entry;
337         }
338         // Attribute is primitive type
339         if ( attributeType == ClassTracker.ClassType.TYPE_PRIMITIVE )
340         {
341            logger.debug("attribute '"+alias+"' found to be a primitive attribute.");
342            // A primitive type should be the last in the list
343            if ( i+1 < attributeSpecifiers.size() )
344               throw new ParserException(ParserException.ABORT,"A primitive type encountered, but not the last in list: "+alias.toString());
345            // Mark primitive type
346            primitiveAttributeSpecifier = spec;
347         }
348      }
349      // If we're here, that means, attribute list has ended.
350      // Either in a primitive type attribute or in object. 
351      SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias.toString());
352      if ( entry == null )
353      {
354         // This means we are in an attribute, so get previous entry
355         entry = previousEntry;
356      }
357      // Construct and return term
358      ReferenceTerm result = null;
359      if ( primitiveAttributeSpecifier != null )
360      {
361         // Attribute specified, this means, we must check whether
362         // this attribute is part of the class in entry or in superclass.
363         // "persistenceid" is considered part of every class, so no superclasses
364         // are selected for that.
365         TableTerm tableTerm = new TableTerm(entry.specifiedTerm);
366         ClassEntry superClassEntry = entry.classInfo.getAttributeClassEntry(primitiveAttributeSpecifier.getIdentifier());
367         if ( logger.isDebugEnabled() )
368            logger.debug("selecting primitive attribute "+primitiveAttributeSpecifier.getIdentifier()+
369                  ", class: "+entry.classInfo.getSourceEntry()+", superclass: "+superClassEntry);
370         if ( (! entry.classInfo.getSourceEntry().equals(superClassEntry)) &&
371           (!"persistenceid".equalsIgnoreCase(primitiveAttributeSpecifier.getIdentifier())) )
372         {
373            // Class holding the attribute differs from entry's class
374            ClassInfo superInfo = classTracker.getClassInfo(superClassEntry);
375            String superTableName = schemaManager.getTableName(superClassEntry);
376            tableTerm = new TableTerm(superTableName,null);
377            if ( ! entry.allocatedSuperTerms.contains(tableTerm) )
378            {
379               logger.debug("primitive attribute is a related class' attribute, and not yet allocated.");
380               // This superclass was not yet allocated
381               Expression joinExpr = new Expression();
382               ReferenceTerm connectLeftTerm = new ReferenceTerm(entry.specifiedTerm,"persistence_id");
383               entry.termList.add(connectLeftTerm);
384               joinExpr.add(connectLeftTerm);
385               joinExpr.add("=");
386               ReferenceTerm connectRightTerm = new ReferenceTerm(tableTerm,"persistence_id");
387               entry.termList.add(connectRightTerm);
388               joinExpr.add(connectRightTerm);
389               SpecifiedTableTerm.LeftjoinEntry leftEntry =  new SpecifiedTableTerm.LeftjoinEntry();
390               leftEntry.term=tableTerm;
391               leftEntry.expression=joinExpr;
392               entry.specifiedTerm.getRelatedLeftTerms().add(leftEntry);
393               // Add to list
394               entry.allocatedSuperTerms.add(tableTerm);
395            }
396         }
397         // Construct result
398         String attributeName = primitiveAttributeSpecifier.getIdentifier();
399         boolean id = false;
400         if ( "persistenceid".equalsIgnoreCase(attributeName) )
401         {
402            attributeName = "persistence_id";
403            id = true;
404         }
405         result = new ReferenceTerm(tableTerm,attributeName);
406         if ( id ) // Mark specifically for id reference
407            result.setId();
408      } else {
409         // Term did not end in attribute specification, so leave
410         // persistence id, which is available in all tables
411         result = getReferenceTerm(entry);
412      }
413      entry.termList.add(result);
414      return result; // Return term
415   }
416 
417   /**
418    * Fix time control to apply transaction time too, if the statement
419    * has common tables with transaction modified tables.
420    */
421   public void fixTimeControl(QueryStatement stmt, Set modifiedTables)
422   {
423      if ( stmt.getTimeControl().isApplyTransaction() )
424         return;
425      // Go through all entries and whatch for tables
426      Iterator entryIterator = symbolTable.values().iterator();
427      while ( entryIterator.hasNext() )
428      {
429         SymbolTableEntry entry = (SymbolTableEntry) entryIterator.next();
430         boolean found = false;
431         if ( modifiedTables.contains(entry.specifiedTerm.getTableName()) )
432            found=true;
433         for ( int i=0; i<entry.specifiedTerm.getRelatedLeftTerms().size(); i++ )
434            if ( modifiedTables.contains(((SpecifiedTableTerm.LeftjoinEntry)
435                        entry.specifiedTerm.getRelatedLeftTerms().get(i)).term.getTableName()) )
436               found=true;
437         for ( int i=0; i<entry.specifiedTerm.getReferencedLeftTerms().size(); i++ )
438            if ( modifiedTables.contains(((SpecifiedTableTerm.LeftjoinEntry)
439                        entry.specifiedTerm.getReferencedLeftTerms().get(i)).term.getTableName()) )
440               found=true;
441         // If found a common table, then set transaction to apply
442         // and return
443         if ( found )
444         {
445            stmt.getTimeControl().setApplyTransaction(true);
446            return; // Transaction applies, common table found
447         }
448      }
449   }
450 
451   /**
452    * Fix atomic expressions for 2VL (2 valued logic), instead of sql 3VL.
453    * This means, that while "a.attr = b.attr" will not work in sql when
454    * both are null, BeanKeeper supposes, that a developer wants that expression
455    * to evaluate to 'true', when both attributes are null.
456    */
457   private Expression fixThreeValuedLogicExpressions(ReferenceTerm term1,
458         ReferenceTerm term2, String op)
459   {
460      if ( logger.isDebugEnabled() )
461         logger.debug("checking for 3VL in following: "+term1+" "+op+" "+term2);
462      Expression result = new Expression();
463      // Check whether both terms refer to non-primitve types, because if at
464      // least one is primitive, then they can't be null anyway
465      ClassInfo info1 = classTracker.getClassInfo(schemaManager.getClassEntry(term1.getTableName()));
466      ClassInfo info2 = classTracker.getClassInfo(schemaManager.getClassEntry(term2.getTableName()));
467      if ( (!term1.getColumnName().equals("persistence_id")) &&
468           (!term2.getColumnName().equals("persistence_id")) &&
469           ( (info1.getAttributeType(term1.getColumnName()).isPrimitive()) ||
470             (info2.getAttributeType(term2.getColumnName()).isPrimitive())) )
471      {
472         // At least one term refers to a primitive type, so that can't be null,
473         // no 3VL logic will interfere with operator
474         result.add(term1);
475         result.add(op);
476         result.add(term2);
477         return result;
478      }
479      // Both terms are referring to non-primitive types, so modify them
480      // to support 'null' comparisons
481      result.setMarkedBlock(true); // Should be in paranthesis
482      if ( op.equals("=") )
483      {
484         // We need to create the following expression:
485         // ( term1=term2 or term1 is null and term2 is null )
486         result.add(term1);
487         result.add("=");
488         result.add(term2);
489         result.add("or");
490         result.add(term1);
491         result.add("is null");
492         result.add("and");
493         result.add(term2);
494         result.add("is null");
495      } else {
496         // We need the following (why is xor missing from sql?):
497         // ( term1<>term2 or term1 is null and term2 is not null or 
498         // term1 is not null and term2 is null )
499         result.add(term1);
500         result.add("<>");
501         result.add(term2);
502         result.add("or");
503         result.add(term1);
504         result.add("is null");
505         result.add("and");
506         result.add(term2);
507         result.add("is not null");
508         result.add("or");
509         result.add(term1);
510         result.add("is not null");
511         result.add("and");
512         result.add(term2);
513         result.add("is null");
514      }
515      return result;
516   }
517 
518   /**
519    * Fix atomic (2 terms, 1 operator) expression artifacts.
520    */
521   public Expression fixAtomicExpressions(Object term1, Object term2, String op)
522   {
523      if ( logger.isDebugEnabled() )
524         logger.debug("fixing atomic expression: "+term1+" <"+op+"> "+term2);
525      // If there is a constant term present, then select it. If
526      // they are both constant terms, then same logic still applies.
527      Expression result = null;
528      Object constantTerm = term2;
529      Object otherTerm = term1;
530      if ( term1 instanceof ConstantTerm )
531      {
532         constantTerm = term1;
533         otherTerm = term2;
534      }
535      // Check whether there is a constant term, which is primitive. In this case
536      // we must check, that it's a primitive or not, because
537      // in this case we may be forced to alter the other term.
538      if ( (otherTerm instanceof ReferenceTerm) &&
539           (constantTerm instanceof ConstantTerm) && 
540           (((ConstantTerm) constantTerm).getValue()!=null) )
541      {
542         result = fixPrimitiveExpression((ReferenceTerm)otherTerm,
543               (ConstantTerm)constantTerm,op);
544         return result;
545      }
546      // If operator is not equals or not-equals, then 3VL conversion is not applicable
547      if ( (!op.equals("=")) && (!op.equals("!=")) && (!op.equals("<>")) )
548      {
549         result = new Expression();
550         result.add(term1);
551         result.add(op);
552         result.add(term2);
553         return result;
554      }
555      // Do 3VL convert logic
556      if ( constantTerm instanceof ConstantTerm )
557      {
558         // There is at least one constant term in the expression
559         if ( ((ConstantTerm)constantTerm).getValue()==null )
560         {
561            // At least one constant term is null in the expression,
562            // so change the operator to fit sql 3VL
563            result = new Expression();
564            result.add(otherTerm);
565            if ( op.equals("=") )
566               result.add("is null");
567            else
568               result.add("is not null");
569         }
570      } else {
571         // Both terms are referenceterms, check 3VL between them
572         if ( (term1 instanceof ReferenceTerm) && (term2 instanceof ReferenceTerm) )
573            result = fixThreeValuedLogicExpressions((ReferenceTerm) term1,
574                  (ReferenceTerm) term2, op);
575      }
576      return result;
577   }
578 
579   /**
580    * Fix a primitive expression.
581    * The problem is expressions like this: find holder where holder.attr = 'Ni'.
582    * If the holder.attr is an object type, and not declared 'primitive'
583    * (in which case it still can hold primitive boxed types), then
584    * the parser can not know it is meant to be primitive only when it
585    * comes to the right term which is primitive constant. So in this case, we
586    * must alter the expression to include the boxed primitive type's table.
587    */
588   private Expression fixPrimitiveExpression(ReferenceTerm leftTerm, 
589         ConstantTerm rightTerm, String op)
590   {
591      // Make the prototype expression
592      Expression result = new Expression();
593      result.add(leftTerm);
594      result.add(op);
595      result.add(rightTerm);
596      // Make a few checks
597      if ( classTracker.getType(rightTerm.getValue().getClass()) !=
598            ClassTracker.ClassType.TYPE_PRIMITIVE )
599         return result; // Not a primitive constant, so there no point
600      if ( rightTerm.isId() || leftTerm.isId() )
601         return result; // Is an Id expression specifically, and not primitive boxes types
602      ClassInfo leftInfo = classTracker.getClassInfo(schemaManager.getClassEntry(leftTerm.getTableName()));
603      if ( (leftInfo!=null) && 
604            (classTracker.getType(leftInfo.getAttributeType(leftTerm.getColumnName()))
605             == ClassTracker.ClassType.TYPE_PRIMITIVE) )
606         return result; // Attribute is primitive, so no modification needed for checking
607      if ( logger.isDebugEnabled() )
608         logger.debug("fixing primitive expression: "+result);
609      // Now insert expression referencing the boxed primitive type's table
610      // Note: We must insert a symbol table entry for the primitive type's
611      // table, but it's not a real symbol, it should have an extremal
612      // name.
613      ClassInfo primitiveInfo = classTracker.getClassInfo(rightTerm.getValue().getClass(),rightTerm.getValue());
614      String primitiveTableName = schemaManager.getTableName(primitiveInfo.getSourceEntry());
615      TableTerm primitiveTableTerm = new TableTerm(primitiveTableName,null);
616      SymbolTableEntry entry = new SymbolTableEntry();
617      entry.specifiedTerm=new SpecifiedTableTerm(primitiveTableName,null);
618      entry.automatic = true;
619      entry.type = SymbolTableEntry.TYPE_OBJECT;
620      symbolTable.put("primitive"+symbolTable.size(),entry);
621      // Create contact expression to primitive table
622      Expression contactExpr = new Expression();
623      contactExpr.add(leftTerm);
624      contactExpr.add("=");
625      ReferenceTerm contactTerm = new ReferenceTerm(primitiveTableTerm,"persistence_id");
626      contactExpr.add(contactTerm);
627      entry.termList.add(contactTerm);
628      entry.expression=contactExpr;
629      // Replace the reference term with new primitive term
630      ReferenceTerm valueTerm = new ReferenceTerm(primitiveTableTerm,"value");
631      entry.termList.add(valueTerm);
632      result.set(0,valueTerm);
633      return result;
634   }
635 
636 
637   private boolean fixContainsNegated(Expression expr, boolean isNegated)
638   {
639      if ( expr == null )
640         return false;
641      boolean originalNegated = isNegated;
642      isNegated=false;
643      for ( int i=0; i<expr.size(); i++ )
644      {
645         Object obj = expr.get(i);
646         if (obj instanceof String)
647         {
648            if ( "contains".equalsIgnoreCase((String) obj) )
649            {
650               if ( isNegated ^ originalNegated )
651                  return true;
652               expr.set(i,"="); // Replace with '=' sign
653            } else
654               isNegated = "not".equalsIgnoreCase((String) obj);
655         }
656         if ( (obj instanceof Expression ) && 
657               (fixContainsNegated((Expression) obj,isNegated ^ originalNegated)) )
658            return true;
659      }
660      return false;
661   }
662 
663   /**
664    * Fix unaliased generated table term.
665    */
666   private Expression fixAutomaticTerms(Expression expr)
667   {
668      // First, generate all temporary names
669      Expression plusExpression = new Expression();
670      String namePrefix = "t_";
671      int tempNameIndex = 1;
672      // Entries will be filtered through a set, because a single
673      // entry could be present in the map multiple times (for example
674      // a main table is available as an alias, and as a table name entry)
675      Iterator iterator = new HashSet(symbolTable.values()).iterator();
676      while ( iterator.hasNext() )
677      {
678         SymbolTableEntry entry = (SymbolTableEntry) iterator.next();
679         // Generate index for class and all superclasses!
680         HashMap tableNameAliases = new HashMap();
681         ArrayList tableTerms = new ArrayList(entry.allocatedSuperTerms);
682         if ( entry.automatic )
683            tableTerms.add(entry.specifiedTerm);
684         for ( int i=0; i<tableTerms.size(); i++ )
685         {
686            TableTerm term = (TableTerm) tableTerms.get(i);
687            while ( symbolTable.get(namePrefix+tempNameIndex) != null)
688               tempNameIndex++;
689            tableNameAliases.put(term.getTableName(),namePrefix+tempNameIndex);
690            term.setAlias(namePrefix+tempNameIndex);
691            tempNameIndex++;
692         }
693         // Walk through referred terms and fill in the gaps
694         for ( int i=0; i<entry.termList.size(); i++ )
695         {
696            TableTerm term = (TableTerm) entry.termList.get(i);
697            String newAlias = (String) tableNameAliases.get(term.getTableName());
698            if ( newAlias != null )
699               term.setAlias(newAlias);
700         }
701         // Remember it's expression
702         if ( (entry.expression != null) && (entry.expression.size()>0) )
703         {
704            if ( logger.isDebugEnabled() )
705               logger.debug("entry '"+entry.specifiedTerm+"' has connector expression: "+entry.expression);
706            if ( plusExpression.size() > 0 )
707               plusExpression.add("and");
708            plusExpression.addAll(entry.expression);
709         }
710      }
711      // Now create final expression
712      Expression result = expr;
713      if ( plusExpression.size() != 0 )
714      {
715         if ( result.size() != 0 )
716         {
717            result = new Expression();
718            result.add(plusExpression);
719            result.add("and");
720            result.add(expr);
721         } else {
722            result = plusExpression;
723         }
724      }
725      // Return
726      return result;
727   }
728 
729   /**
730    * Add all left table terms to term, all terms are at this point fixed.
731    * Note that method does not directly use the related classes returned 
732    * by the classtracker, because the supplied leftterms already may
733    * contain aliases that couldn't be recovered elsewhere.
734    */
735   private void fixLeftTableTerms(ClassInfo info, SpecifiedTableTerm term, List leftTerms)
736   {
737      // Calculate left table terms
738      Set relatedClassEntries = new HashSet(classTracker.getRelatedClassEntries(info.getSourceEntry()));
739      for ( int i=0; i<leftTerms.size(); i++ )
740      {
741         // Go through left term, if this is a left term to the info, then
742         // insert it to this term's left terms.
743         // Note: class info can be null, if creating the table for that class
744         // previously failed.
745         TableTerm leftTerm = ((SpecifiedTableTerm.LeftjoinEntry) leftTerms.get(i)).term;
746         ClassInfo classInfo = classTracker.getClassInfo(schemaManager.getClassEntry(leftTerm.getTableName()));
747         if ( classInfo != null )
748         {
749            ClassEntry leftEntry = classInfo.getSourceEntry();
750            if ( relatedClassEntries.contains(leftEntry) )
751            {
752               SpecifiedTableTerm.LeftjoinEntry entry = new SpecifiedTableTerm.LeftjoinEntry();
753               entry.term=leftTerm;
754               term.getRelatedLeftTerms().add(entry);
755               Expression joinExpr = new Expression();
756               joinExpr.add(new ReferenceTerm(term,"persistence_id"));
757               joinExpr.add("=");
758               joinExpr.add(new ReferenceTerm(leftTerm,"persistence_id"));
759               entry.expression=joinExpr;
760            }
761         }
762      }
763   }
764   
765   /**
766    * Get the left table terms, which will contained in the select. These are all
767    * super-, and sub-classes of the specified term.  This method can be used on the
768    * selected term, which has to include all of these to return all relevant attributes.
769    * Note: all related classes will be included in the allocatedSuperTerms attribute,
770    * which is not correct, since it contains subtables too.
771    */
772   private void fixLeftTableTerms(SpecifiedTableTerm term, SymbolTableEntry mainEntry)
773   {
774      // Calculate left table terms
775      List relatedClassEntries = classTracker.getRelatedClassEntries(mainEntry.classInfo.getSourceEntry());
776      if ( logger.isDebugEnabled() )
777         logger.debug("found related classes: "+relatedClassEntries+", to class info: "+mainEntry.classInfo);
778      for ( int i=0; i<relatedClassEntries.size(); i++ )
779      {
780         ClassEntry relatedClassEntry = (ClassEntry) relatedClassEntries.get(i);
781         ClassInfo relatedClassInfo = classTracker.getClassInfo(relatedClassEntry);
782         if ( logger.isDebugEnabled() )
783            logger.debug("found left joined class: "+relatedClassEntry+", class info: "+relatedClassInfo);
784         if ( relatedClassInfo == null )
785            throw new ParserException(ParserException.ABORT,"object class not found for loading: '"+relatedClassEntry+"'");
786         // Create the term and add to mainterm
787         TableTerm leftTerm = new TableTerm(schemaManager.getTableName(relatedClassEntry),null);
788         if ( logger.isDebugEnabled() )
789            logger.debug("adding left term to: "+mainEntry.specifiedTerm+", left term: "+leftTerm);
790         mainEntry.termList.add(leftTerm);
791         mainEntry.allocatedSuperTerms.add(leftTerm);
792         // Add left join entry to the specified term
793         SpecifiedTableTerm.LeftjoinEntry entry = new SpecifiedTableTerm.LeftjoinEntry();
794         mainEntry.specifiedTerm.getRelatedLeftTerms().add(entry);
795         entry.term=leftTerm;
796         Expression joinExpr = new Expression();
797         entry.expression=joinExpr;
798         ReferenceTerm toTerm = new ReferenceTerm(term,"persistence_id");
799         mainEntry.termList.add(toTerm);
800         joinExpr.add(toTerm);
801         joinExpr.add("=");
802         ReferenceTerm valueTerm = new ReferenceTerm(leftTerm,"persistence_id");
803         mainEntry.termList.add(valueTerm);
804      }
805   }
806   
807   /**
808    * Add date constraint expressions.
809    */
810   private Expression fixDateConstraints(Collection localAllTableTerms, Expression expr, TimeControl timeControl)
811   {
812      if ( logger.isDebugEnabled() )
813         logger.debug("fixing date constraints for tables: "+localAllTableTerms);
814      Expression dateExpression = new Expression();
815      // Generate date constraints for all tables which are not left-join tables
816      Iterator tablesIterator = localAllTableTerms.iterator();
817      while ( tablesIterator.hasNext() )
818      {
819         SpecifiedTableTerm term = (SpecifiedTableTerm) tablesIterator.next();
820         // Add to date constraints
821         if ( logger.isDebugEnabled() )
822            logger.debug("adding date constraint to term: "+term);
823         if ( dateExpression.size() != 0 )
824            dateExpression.add("and");
825         timeControl.apply(dateExpression,term);
826         // Fix term's left join expressions too
827         for ( int o=0; o<term.getRelatedLeftTerms().size(); o++ )
828         {
829            SpecifiedTableTerm.LeftjoinEntry entry = (SpecifiedTableTerm.LeftjoinEntry) term.getRelatedLeftTerms().get(o);
830            entry.expression.add("and");
831            timeControl.apply(entry.expression,entry.term);
832            if ( logger.isDebugEnabled() )
833               logger.debug("adding date constraint to left term: "+entry.term);
834         }
835         for ( int o=0; o<term.getReferencedLeftTerms().size(); o++ )
836         {
837            SpecifiedTableTerm.LeftjoinEntry entry = (SpecifiedTableTerm.LeftjoinEntry) term.getReferencedLeftTerms().get(o);
838            entry.expression.add("and");
839            timeControl.apply(entry.expression,entry.term);
840            if ( logger.isDebugEnabled() )
841               logger.debug("adding date constraint to left join term term: "+entry.term);
842         }
843      }
844      // Return
845      Expression result = expr;
846      if ( dateExpression.size() != 0 )
847      {
848         if ( (result!=null) && (result.size() != 0) )
849         {
850            result = new Expression();
851            result.add(dateExpression);
852            result.add("and");
853            result.add(expr);
854         } else {
855            result = dateExpression;
856         }
857      }
858      // Return
859      return result;
860   }
861 
862   private boolean validViewSelectTables(QueryStatement stmt)
863   {
864      if ( stmt.getMode() == QueryStatement.MODE_FIND )
865         return true; // Valid, because it is in find mode
866      // Check whether all selected terms are storable
867      for ( int i=0; i<stmt.getSelectTerms().size(); i++ )
868      {
869         TableTerm term = (TableTerm) stmt.getSelectTerms().get(i);
870         ClassInfo info = classTracker.getClassInfo(schemaManager.getClassEntry(term.getTableName()));
871         if ( ! info.isStorable() )
872            return false;
873      }
874      return true;
875   }
876 
877   private void fixNonstorableTerms(List selectTerms, Expression expr)
878   {
879      if ( logger.isDebugEnabled() )
880         logger.debug("fixing nonstorable terms in expression: "+expr);
881      // Go through all terms, and those which are not storable and
882      // not selected should cause an error. This is because since there is
883      // no ids table, we can not determine what to write in the place of
884      // these terms. On some occasions, it would be possible to substitute
885      // them, but not generally.
886      // Note: this didn't work correctly with ids table either, because
887      // the time control was not used in these cases! (On the abstract
888      // object)
889      if ( expr == null )
890         return;
891      for ( int i=0; i<expr.size(); i++ )
892      {
893         Object value = expr.get(i);
894         if ( value instanceof Expression )
895         {
896            fixNonstorableTerms(selectTerms,(Expression) value);
897         } else if ( value instanceof TableTerm ) {
898            // Found a table term
899            TableTerm term = (TableTerm) value;
900            ClassInfo info = classTracker.getClassInfo(schemaManager.getClassEntry(term.getTableName()));
901            if ( info == null )
902               continue; // No worries, most likely an internal table
903            ClassEntry entry = info.getSourceEntry();
904            if ( (!selectTerms.contains(term)) && (!info.isStorable()) )
905            {
906               // A non-storable entry's term found which is not selected,
907               // so throw error.
908               throw new ParserException(ParserException.ABORT,"found non-storable, non-selected term: '"+term+"'. "+
909                     "Non-storable classes can not be used if they are not the subject of the select.");
910            }
911         }
912      }
913   }
914 
915   private void fixOrderBys(QueryStatement stmt)
916   {
917      if ( stmt.getOrderByList() == null )
918         stmt.setOrderByList(new ArrayList());
919      if ( stmt.getMode() == QueryStatement.MODE_FIND )
920      {
921         // Find mode: add default order by on persistence_id
922         TableTerm mainTerm = (TableTerm) stmt.getSelectTerms().get(0);
923         OrderBy orderBy = new OrderBy(new ReferenceTerm(mainTerm,"persistence_id"),OrderBy.ASCENDING);
924         if ( ! stmt.getOrderByList().contains(orderBy) )
925            stmt.getOrderByList().add(orderBy);
926      } else {
927         // View mode: add all view attributes as order bys, so
928         // listing becomes unambigous.
929         for ( int i=0; i<stmt.getSelectTerms().size(); i++ )
930         {
931            ReferenceTerm term = (ReferenceTerm) stmt.getSelectTerms().get(i);
932            OrderBy orderBy = new OrderBy(term,OrderBy.ASCENDING);
933            if ( ! stmt.getOrderByList().contains(orderBy) )
934               stmt.getOrderByList().add(orderBy);
935         }
936      }
937      if ( logger.isDebugEnabled() )
938         logger.debug("fixed order by list to: "+stmt.getOrderByList());
939   }
940 
941   private void validateAggregateClauses(QueryStatement stmt)
942   {
943      // If this statement has "having" and "group by" clauses
944      // then it must be a "view" query
945      if ( (((stmt.getHavingExpression()!=null) && (!stmt.getHavingExpression().isEmpty())) ||
946            ((stmt.getGroupByList()!=null) && (!stmt.getGroupByList().isEmpty()))) &&
947            stmt.getMode() == QueryStatement.MODE_FIND )
948         throw new ParserException(ParserException.ABORT,"query has 'group by' or 'having' clauses as a 'find' query, only 'view' queries can be aggregate");
949      // If the group by list is not empty, then all references in the group by list
950      // must be a non-aggregated term in the select list, and all the others in the
951      // select list must be aggregated
952      if ( (stmt.getGroupByList()!=null) && (! stmt.getGroupByList().isEmpty()) )
953      {
954         // Make a copy
955         List<ReferenceTerm> selectTerms = new ArrayList<ReferenceTerm>(stmt.getSelectTerms());
956         if ( logger.isDebugEnabled() )
957            logger.debug("checking select terms for aggregation with group by clause: "+selectTerms);
958         // Check those in the group by list
959         for ( ReferenceTerm term : (List<ReferenceTerm>)stmt.getGroupByList() )
960         {
961            // Search for this term in the select list. If found
962            // it must be non-aggregate
963            Iterator<ReferenceTerm> selectTermsIterator = selectTerms.iterator();
964            while ( selectTermsIterator.hasNext() )
965            {
966               ReferenceTerm selectTerm = selectTermsIterator.next();
967               if ( (term.equals(selectTerm)) && (term.getColumnName().equals(selectTerm.getColumnName())) )
968               {
969                  if ( selectTerm.getFunction() != null )
970                     throw new ParserException(ParserException.ABORT,"term '"+selectTerm+
971                           "' must not have a function on it, since it's also in the 'group by' clause");
972                  else
973                     selectTermsIterator.remove();
974               }
975            }
976         }
977         // Check the rest, that they are aggregate
978         for ( ReferenceTerm selectTerm : selectTerms )
979         {
980            if ( (selectTerm.getFunction()==null) || 
981                  (! (selectTerm.getFunction() instanceof AggregateFunction)) )
982               throw new ParserException(ParserException.ABORT,"select term '"+selectTerm+"' must be aggregate, since it's not in the 'group by' clause");
983         }
984      } else {
985         // If there is no group by list, then make sure all selected terms are non-aggregate
986         if ( logger.isDebugEnabled() )
987            logger.debug("checking selected terms for aggregated ones, there is no group by statement");
988         boolean foundNonAggregate = false;
989         boolean foundAggregate = false;
990         for ( Object selectTerm : stmt.getSelectTerms() )
991         {
992            if ( ! (selectTerm instanceof ReferenceTerm) )
993               continue;
994            ReferenceTerm selectRefTerm = (ReferenceTerm) selectTerm;
995            if ( (selectRefTerm.getFunction()!=null) && 
996                  (selectRefTerm.getFunction() instanceof AggregateFunction) )
997               foundAggregate = true;
998            else
999               foundNonAggregate = true;
1000         }
1001         if ( (foundAggregate) && (foundNonAggregate) )
1002            throw new ParserException(ParserException.ABORT,"there are aggregate and non-aggregate columns in the selection, but there is no group-by clause");
1003      }
1004      // Validate having clause. It must contain only reference terms that are either in the
1005      // group by clause and not aggregated, or aggregated.
1006      if ( (stmt.getHavingExpression()!=null) && (! stmt.getHavingExpression().isEmpty()) )
1007      {
1008         List openTerms = new ArrayList();
1009         openTerms.addAll(stmt.getHavingExpression());
1010         while ( ! openTerms.isEmpty() )
1011         {
1012            // Get one
1013            Object term = openTerms.remove(0);
1014            // Handle
1015            if ( term instanceof Expression )
1016               openTerms.addAll((Expression) term);
1017            if ( term instanceof ReferenceTerm )
1018            {
1019               ReferenceTerm refTerm = (ReferenceTerm) term;
1020               if ( (refTerm.getFunction() == null) || (!(refTerm.getFunction() instanceof AggregateFunction)) )
1021               {
1022                  // This is not an aggregated reference term, therefore it must be in the
1023                  // group by clause
1024                  boolean foundTerm = false;
1025                  for ( ReferenceTerm groupTerm : (List<ReferenceTerm>) stmt.getGroupByList() )
1026                     if ( (groupTerm.equals(refTerm)) && (groupTerm.getColumnName().equals(refTerm.getColumnName())) )
1027                        foundTerm = true;
1028                  if ( ! foundTerm )
1029                     throw new ParserException(ParserException.ABORT,"term in having clause '"+term+
1030                           "' was not aggregated and also not part of the 'group by' clause");
1031               }
1032            }
1033         }
1034      }
1035   }
1036 
1037   /**
1038    * Generate the final statements.
1039    */
1040   public QueryStatementList generate(QueryStatement stmt)
1041   {
1042      Expression expr = stmt.getQueryExpression();
1043      if ( expr == null )
1044         expr = new Expression();
1045      if ( logger.isDebugEnabled() )
1046      {
1047         logger.debug("symbol table before finalizing: "+symbolTable);
1048         logger.debug("expression before finalizing: "+expr);
1049      }
1050      // Add default order bys
1051      fixOrderBys(stmt);
1052      // Check whether 'contains' operator is negated (this is not allowed)
1053      // if it does not, then substitute with '=' sign.
1054      if ( fixContainsNegated(expr,false) )
1055         throw new ParserException(ParserException.ABORT,"'contains' operator is negated, this may not mean what you think it means, so it's disallowed.");
1056      // If this is a view select, check whether all selected terms
1057      // are storable.
1058      if ( ! validViewSelectTables(stmt) )
1059         throw new ParserException(ParserException.ABORT,"view selects can not contain non-storable terms "+
1060               "(ie. interface types and abstract objects as selected terms).");
1061      // Generate all the specified terms the query will use. Note that
1062      // leftterms (neither super-,sub-classes nor referenced terms)
1063      // Note: at this point, the tables have no aliases yet, so keep'em
1064      // in list not set.
1065      List allTableTerms = new ArrayList();
1066      Iterator entryIterator = symbolTable.values().iterator();
1067      while ( entryIterator.hasNext() )
1068      {
1069         SymbolTableEntry entry = (SymbolTableEntry) entryIterator.next();
1070         SpecifiedTableTerm term = entry.specifiedTerm;
1071         if ( ! entry.leftTerm )
1072            allTableTerms.add(term);
1073      }
1074      // Generate aliases and connector expressions
1075      stmt.setQueryExpression(fixAutomaticTerms(expr));
1076      // Check sanity of aggregate parts
1077      validateAggregateClauses(stmt);
1078      // Fix all non-storable terms that are used in the expression but
1079      // not selected.
1080      fixNonstorableTerms(stmt.getSelectTerms(),stmt.getQueryExpression());
1081      // Now generate all the root queries for this query.
1082      // View selects can not contain non-storable terms, and find
1083      // selects can only contain 1 main term, so get the roots for
1084      // this main term, and iterate over it's roots.
1085      QueryStatementList stmts = new QueryStatementList();
1086      SpecifiedTableTerm selectTerm = mainTerm;
1087      ClassEntry selectEntry = schemaManager.getClassEntry(selectTerm.getTableName());
1088      List roots = classTracker.getStorableRootClassEntries(selectEntry);
1089      // Prepare local lists, these will be used later
1090      Set localAllTableTermsCore = new HashSet(allTableTerms);
1091      localAllTableTermsCore.remove(selectTerm);
1092      if ( logger.isDebugEnabled() )
1093         logger.debug("determined roots for "+selectEntry+": "+roots);
1094      // Now generate the statements themselves
1095      for ( int r=0; r<roots.size(); r++ )
1096      {
1097         // Clone statement
1098         QueryStatement subStmt = stmt.deepCopy();
1099         Set localAllTableTerms = new HashSet(localAllTableTermsCore);
1100         // The counter determines with which root to replace select term with
1101         ClassEntry rootEntry = (ClassEntry) roots.get(r);
1102         if ( logger.isDebugEnabled() )
1103            logger.debug("entry "+selectEntry+" is to be replaced with root entry: "+rootEntry);
1104         ClassInfo rootInfo = classTracker.getClassInfo(rootEntry);
1105         SpecifiedTableTerm rootTerm = new SpecifiedTableTerm(schemaManager.getTableName(rootEntry),
1106               selectTerm.getAlias());
1107         rootTerm.setReferencedLeftTerms(selectTerm.getReferencedLeftTerms()); // Keep referenced terms
1108         fixLeftTableTerms(rootInfo,rootTerm,selectTerm.getRelatedLeftTerms()); // Add left table terms
1109         if ( logger.isDebugEnabled() )
1110            logger.debug("entry "+selectEntry+" ("+selectTerm+") is replaced with root entry: "+rootEntry+" ("+rootTerm+")");
1111         // Do replace operation in statement. This will replace
1112         // the term everywhere.
1113         subStmt.replace(selectTerm,rootTerm);
1114         // The specified tables are stored in the query itself
1115         localAllTableTerms.add(rootTerm);
1116         subStmt.setSpecifiedTerms(localAllTableTerms);
1117         if ( logger.isDebugEnabled() )
1118            logger.debug("statement will have specified terms: "+localAllTableTerms);
1119         // Generate static representation (it is important, that this is
1120         // before date constraints are added
1121         subStmt.setStaticRepresentation(subStmt.getMode()+" "+
1122               subStmt.getSelectTerms().toString()+" "+
1123               subStmt.getSpecifiedTerms().toString()+" "+
1124               (subStmt.getQueryExpression()!=null?subStmt.getQueryExpression().toString():"")+
1125               (subStmt.getGroupByList()!=null?subStmt.getGroupByList().toString():"")+
1126               (subStmt.getHavingExpression()!=null?subStmt.getHavingExpression().toString():"")+
1127               (subStmt.getOrderByList()!=null?subStmt.getOrderByList().toString():""));
1128         // Generate date constraints
1129         subStmt.setQueryExpression(
1130               fixDateConstraints(localAllTableTerms,
1131                  subStmt.getQueryExpression(), subStmt.getTimeControl()));
1132         // Debug
1133         if ( logger.isDebugEnabled() )
1134            logger.debug("generated statement, selected: "+subStmt.getSelectTerms()+
1135                  ", specified: "+subStmt.getSpecifiedTerms()+
1136                  ", expression: "+subStmt.getQueryExpression()+
1137                  ", group by: "+subStmt.getGroupByList()+
1138                  ", having: "+subStmt.getHavingExpression()+
1139                  ", order by: "+subStmt.getOrderByList());
1140         // Insert complete query into result query list
1141         stmts.add(subStmt);
1142      }
1143      // Now generate the query which will return all table names
1144      if ( stmts.size() > 1 )
1145      {
1146         // Clone statement
1147         QueryStatement subStmt = stmt.deepCopy();
1148         // Set specified terms
1149         subStmt.getSpecifiedTerms().addAll(localAllTableTermsCore);
1150         // Generate static representation (it is important, that this is
1151         // before date constraints are added
1152         subStmt.setStaticRepresentation(subStmt.getMode()+" "+
1153               subStmt.getSelectTerms().toString()+" "+
1154               subStmt.getSpecifiedTerms().toString()+" "+
1155               (subStmt.getQueryExpression()!=null?subStmt.getQueryExpression().toString():"")+
1156               (subStmt.getOrderByList()!=null?subStmt.getOrderByList().toString():""));
1157         // Generate date constraints on all but the main term
1158         subStmt.setQueryExpression(
1159               fixDateConstraints(localAllTableTermsCore,
1160                  subStmt.getQueryExpression(), subStmt.getTimeControl()));
1161         if ( logger.isDebugEnabled() )
1162            logger.debug("generated root statement, selected: "+subStmt.getSelectTerms()+
1163                  ", specified: "+subStmt.getSpecifiedTerms()+
1164                  ", expression: "+subStmt.getQueryExpression()+", order by: "+subStmt.getOrderByList());
1165         // Add
1166         stmts.setRoot(subStmt);
1167      }
1168      return stmts;
1169   }
1170 
1171   public static class SymbolTableEntry
1172   {
1173      public static final int TYPE_HANDLED = 1;
1174      public static final int TYPE_OBJECT = 3;
1175      public static final int TYPE_PRIMITIVE = 4;
1176      
1177      public SpecifiedTableTerm specifiedTerm; 
1178                                 // The term this entry represents, if any
1179      public String referenceColumn;
1180                                 // Used by handled types, definies the
1181                                 // reference column, if sub-specified.
1182      public boolean selected;   // Whether this entry will be selected
1183      public List termList;      // List of terms in which could not
1184                                 // be resolved yet
1185      public List allocatedSuperTerms;
1186                                 // The list of terms of superclasses
1187                                 // already allocated (used)
1188      public ClassInfo classInfo;// Class info if symbol is a table
1189      public boolean automatic;  // Whether symbol should be automatically
1190                                 // generated
1191      public boolean leftTerm;   // Whether this term is standalone, or 
1192                                 // depending on another term
1193      public Expression expression;
1194                                 // The expression this entry generated
1195      public int type = TYPE_OBJECT;
1196 
1197      public SymbolTableEntry()
1198      {
1199         termList = new LinkedList();
1200         allocatedSuperTerms = new LinkedList();
1201         leftTerm=false;
1202      }
1203 
1204      public String toString()
1205      {
1206         return "[Symbol entry: "+specifiedTerm+", "+type+":"+automatic+":"+leftTerm+"]";
1207      }
1208   }
1209}
1210 

[all classes][hu.netmind.beankeeper.parser]
EMMA 2.0.5312debian (C) Vladimir Roubtsov