View Javadoc

1   /* (c) Copyright 2003 Caleigo AB, All rights reserved. 
2    * 
3    * This library is free software; you can redistribute it and/or
4    * modify it under the terms of the GNU Lesser General Public
5    * License as published by the Free Software Foundation; either
6    * version 2.1 of the License, or (at your option) any later version.
7    * 
8    * This library is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   * Lesser General Public License for more details.
12   * 
13   * You should have received a copy of the GNU Lesser General Public
14   * License along with this library; if not, write to the Free Software
15   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   *  
17   */
18  package org.caleigo.core.service;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.ListIterator;
26  import java.util.Map;
27  
28  import org.caleigo.core.CompositeEntityDescriptor;
29  import org.caleigo.core.DataQuery;
30  import org.caleigo.core.EntityRelationPath;
31  import org.caleigo.core.ICompositeEntityDescriptor;
32  import org.caleigo.core.IEntityDescriptor;
33  import org.caleigo.core.IEntityRelationPath;
34  
35  
36  /*** Instances of this class is used to get as few uniq aliases as possible  
37   * in a SQL SELECT statement's SELECT-, FROM-, JOIN- and WHERE-
38   * expressions/clauses.
39   *  
40   * @author Niklas Norberg
41   */
42  public class SQLSelectCommand
43  {
44      //nno-todo Kommentera mera
45      // Data members --------------------------------------------------------
46      private StringBuffer mSQLCommand = new StringBuffer(2000);
47      private String mRootAlias;
48      private HashMap mUniqAliasMap = new HashMap();
49      private HashMap mDeclaredAliasMap = new HashMap(); 
50      private EntityRelationPathAliasTree mEntityRelationPathAliasTree = new EntityRelationPathAliasTree();
51      
52      // Access methods ------------------------------------------------------
53      
54      protected String getRootAlias(){
55          return mRootAlias;
56      }
57      
58      public String toString(){
59          return mSQLCommand.toString();
60      }
61          
62      private String getAvailabeAlias(String suggestedAlias)
63      {
64          for(;;)
65          {
66              if( mUniqAliasMap.containsKey(suggestedAlias) )
67                  suggestedAlias = CompositeEntityDescriptor.increaseStringEndingWithNumber(suggestedAlias);
68              else
69                  break;
70          }
71          return suggestedAlias;
72      }
73      
74      private boolean isAliasTaken(String alias)
75      {
76          if( mUniqAliasMap.containsKey(alias) )
77              return true;
78          else
79              return false;
80      }
81      
82      protected boolean isAliasDeclared(String alias)
83      {
84          if( mDeclaredAliasMap.containsKey(alias) )
85              return true;
86          else
87              return false;
88      }
89      
90          
91      private void printUniqAliases()
92      {
93          Iterator it = mUniqAliasMap.keySet().iterator();
94          while(it.hasNext())
95              System.out.println(it.next());
96      }
97      
98      /*** Get uniq aliases for a path.
99       * 
100      * @param entityRelationPath a path to get uniq aliases for.
101      * @return a String-array containing uniq aliases for the path.
102      */
103     protected String[] getAliasArray(IEntityRelationPath entityRelationPath)
104     {
105         return mEntityRelationPathAliasTree.getAliasArray(entityRelationPath);
106     }
107         
108     // Mutable methods -----------------------------------------------------
109         
110     protected void append(String str)
111     {
112         mSQLCommand.append(str);
113     }
114         
115     protected void append(char c)
116     {
117         mSQLCommand.append(c);
118     }
119     
120     protected void shortenCommandWith(int shortenThisMuch)
121     {
122         mSQLCommand.setLength(mSQLCommand.length()-shortenThisMuch);
123     }
124     
125     protected void setRootAlias(String rootAlias){
126         mRootAlias = rootAlias;
127         this.markAliasAsUniq(rootAlias);
128     }
129         
130     private void markAliasAsUniq(String uniqAlias)
131     {
132         if( !mUniqAliasMap.containsKey(uniqAlias) )
133             mUniqAliasMap.put(uniqAlias, null);
134         else
135             throw new RuntimeException("The alias: "+uniqAlias+" isn't uniq!");
136     }
137     
138     protected void markAliasAsDeclared(String declaredAlias)
139     {
140         if( !mDeclaredAliasMap.containsKey(declaredAlias) )
141             mDeclaredAliasMap.put(declaredAlias, null);
142         else
143             throw new RuntimeException("The alias: "+declaredAlias+
144                     "have already been declared once!");
145     }
146     
147     /***
148      * 
149      * @param entityRelationPathMapForFieldGroups
150      */
151     private void addAndMakeUniqueAliasForPathsInMap(Map entityRelationPathMapForFieldGroups)
152     {
153         //Make a list sorted in size order, shortest first
154         LinkedList entityRelationPathlist = new LinkedList();
155         Iterator it = entityRelationPathMapForFieldGroups.keySet().iterator();
156         //add the first one
157         if(it.hasNext())
158             entityRelationPathlist.add( (EntityRelationPath)it.next() );
159         //add the rest if any
160         while(it.hasNext())
161         {
162             IEntityRelationPath entityRelationPath = (IEntityRelationPath)it.next();
163             
164             for(int i=0;i<entityRelationPathlist.size();i++)
165             {
166                 if( entityRelationPath.getRelationCount() <= ((IEntityRelationPath)entityRelationPathlist.get(i)).getRelationCount() )
167                 {
168                     entityRelationPathlist.add(i, entityRelationPath);
169                     break;
170                 }
171                 else if ( i == entityRelationPathlist.size()-1 )
172                 {
173                     //It's the for loop's last iteration-cycle let's just add it
174                     entityRelationPathlist.add(entityRelationPath);
175                     break;
176                 }
177             }
178         }
179         
180 //        System.out.println("\n\n\n\n\n\n\n\n\n");
181         // We must do this in length order to make sure that we always use alias
182         // that were set manually
183         ListIterator listIter = entityRelationPathlist.listIterator();
184         while(listIter.hasNext())
185         {
186             IEntityRelationPath entityRelationPath = (IEntityRelationPath)listIter.next();
187             String[] aliases = (String[])entityRelationPathMapForFieldGroups.get(entityRelationPath);
188 //            for (int i = 0; i < aliases.length; i++)
189 //                System.out.print(aliases[i]+"  -  ");
190 //            System.out.println("\n\n");
191             mEntityRelationPathAliasTree.makeUniqAliasForPath(entityRelationPath, aliases);
192         }
193     }
194     
195     
196     /*** This method collects all entity relation path in a field group tree.
197      * String-arrays are created and paired with every path.
198      * Lastly every path are added to the internal member
199      * mEntityRelationPathAliasTree and uniq alias are made for slave entities.
200      * 
201      * @param rootFieldGroup the root in the field group tree.
202      */
203     protected void collectAllEntityRelationPathsAndMakeUniqAliases(ICompositeEntityDescriptor.ICompositeFieldGroup rootFieldGroup)
204     {
205         HashMap entityRelationPathMapForFieldGroups = new HashMap();
206         
207         //The last parameter (the Map) gets filled with values.
208         this.collectAllEntityRelationPaths(rootFieldGroup, null, new ArrayList(), entityRelationPathMapForFieldGroups);
209         
210         this.addAndMakeUniqueAliasForPathsInMap(entityRelationPathMapForFieldGroups);
211         
212 //        this.printAllPathsAndBelongingArray();//dbg-printing
213           
214     }
215     
216     /*** This method make recursive calls to itself and thereby collects paths
217      * and alias lists in a field group tree.
218      * 
219      * @param parent root for a field group tree.
220      * @param entityRelationPath the path to append to (appending is done to a
221      * copy), (should be null for the root).
222      * @param aliasList the list to append aliases to (appending is done to a
223      * copy).
224      * @param entityRelationPathMapForFieldGroups a Map with  entity relation
225      * paths as keys and alias as values. The Map is changed by this method.
226      */
227     private void collectAllEntityRelationPaths(final ICompositeEntityDescriptor.ICompositeFieldGroup parent,
228                                                 final EntityRelationPath entityRelationPath,
229                                                 final List aliasList,
230                                                 Map entityRelationPathMapForFieldGroups)
231     {
232         if (entityRelationPath !=null )
233         {//It's not the root let's add path and it's belonging aliasList
234             
235             //put aliases in the map with the entity relation path as key
236             entityRelationPathMapForFieldGroups.put(entityRelationPath, aliasList.toArray(new String[]{})); 
237             
238             //DEBUG OBS how we use the fact that a copy has the same hachcode!!!
239             //EntityRelationPath entityRelationPath_COPY = new EntityRelationPath(entityRelationPath);
240             //String[] stringArrayRetreived = (String[])entityRelationPathMapForFieldGroups.get(entityRelationPath_COPY);
241             //System.out.println(stringArrayRetreived);
242             //end-DEBUG
243         }
244         for(int j=0; j<parent.getChildFieldGroupCount(); j++)
245         {
246             ICompositeEntityDescriptor.ICompositeFieldGroup child = parent.getChildFieldGroup(j);
247             EntityRelationPath entityRelationPath2Append2;
248             //make a copy and append to this copy and send it further
249             ArrayList aliasList2Append2 = new ArrayList(aliasList);
250             if(entityRelationPath ==null)
251             {
252                 entityRelationPath2Append2 = new EntityRelationPath(child.getParentRelationPath());
253                 aliasList2Append2.add(mRootAlias!=null?mRootAlias:parent.getEntityIdentity());
254             }
255             else
256             {
257                 //make a copy and append to this copy and send it further
258                 entityRelationPath2Append2 = new EntityRelationPath(entityRelationPath);
259                 entityRelationPath2Append2.appendRelationPath(new EntityRelationPath(child.getParentRelationPath()));
260             }
261             for (int i = 0; i < child.getParentRelationPath().getRelationCount()-1; i++)
262                 aliasList2Append2.add(null);//null only temporarely
263             
264             // TRICKY make sure that supplied entity identities
265             // ( i.e. when calling fgBuilder.beginFieldGroup(region_EntityDescriptor, "Region_1"); )
266             // are used:
267             if( child.getEntityIdentity() != child.getBaseEntityDescriptor().getCodeName() )
268             {
269                 this.markAliasAsUniq(child.getEntityIdentity());
270                 aliasList2Append2.add(child.getEntityIdentity());
271                 //To test code (at other place) that should make aliases for nulls
272                 //disable the two above lines and uncomment the following line
273                 //aliasList2Append2.add(null);//dbg
274             }
275             else
276                 aliasList2Append2.add(null);//null only temporarely
277             
278             //make the recursive call
279             this.collectAllEntityRelationPaths(child, entityRelationPath2Append2, aliasList2Append2, entityRelationPathMapForFieldGroups);
280         }
281     }
282     
283     
284     
285     
286     
287     /*** This method collects paths and alias lists in an external qualifer and
288      * make uniq alias.
289      * 
290      * @param xTernalQualifiers the external qualifer to collect paths and alias
291      * from. 
292      */
293     protected void collectAllEntityRelationPathsAndMakeUniqAliases(DataQuery.ExternalQualifier[] xTernalQualifiers)
294     {
295         HashMap entityRelationPathMapForXternalQualifiers = new HashMap();
296         
297         //The last parameter (the Map) gets filled with values.
298         this.collectAllEntityRelationPaths(xTernalQualifiers, entityRelationPathMapForXternalQualifiers);
299         
300         this.addAndMakeUniqueAliasForPathsInMap(entityRelationPathMapForXternalQualifiers);
301         
302 //        this.printAllPathsAndBelongingArray();//dbg-printing
303         
304     }
305     
306     /*** This method collects paths and alias lists in an external qualifer.
307      * 
308      * @param xTernalQualifiers the external qualifer to collect paths and alias
309      * from. 
310      */    
311     private void collectAllEntityRelationPaths(DataQuery.ExternalQualifier[] xTernalQualifiers, Map entityRelationPathMapForXternalQualifiers)
312     {
313         for (int i = 0; i < xTernalQualifiers.length; i++)
314         {
315             IEntityRelationPath path2Qualifer =  xTernalQualifiers[i].getEntityRelationPath();
316             String[] aliases = new String[path2Qualifer.getRelationCount()+1];
317             
318             //set first alias in the alias array.
319             IEntityDescriptor firstEntityDescriptorInPath = path2Qualifer.getFirstEntityDescriptor();
320             if(firstEntityDescriptorInPath instanceof ICompositeEntityDescriptor)
321             {
322                 Iterator iter = path2Qualifer.getFirstFieldDescriptors();
323                 //should have been validated so the first one should have the same field group as the rest so first one is as good as anyone.
324                 firstEntityDescriptorInPath = ((ICompositeEntityDescriptor.ICompositeFieldDescriptor) iter.next()).getFieldGroup().getBaseEntityDescriptor();
325                 
326             }
327             //Get an uniq alias:
328             String uniqAlias = SQLSelectCommand.this.getAvailabeAlias(firstEntityDescriptorInPath.getCodeName());
329             //then mark alias as uniq
330             SQLSelectCommand.this.markAliasAsUniq(uniqAlias);
331             //and use it!
332             aliases[0] = uniqAlias;
333             
334             //set last alias in the alias array.
335             aliases[aliases.length-1] = mRootAlias;
336             
337             //put aliases in the map with the entity relation path as key
338             entityRelationPathMapForXternalQualifiers.put(path2Qualifer, aliases);
339         }
340     }
341     
342     
343     
344     
345     
346     
347     
348     
349     
350     
351     /*** Debug-print method. */
352     private void printAllPathsAndBelongingArray()
353     {
354         Map uniqAliasMap = mEntityRelationPathAliasTree.getUniqAliasMap();
355         mEntityRelationPathAliasTree.printAllPathsAndBelongingArray(uniqAliasMap);
356     }
357     
358     
359     
360     /*** Instances of this class should hold all possibly parts (subpaths) of
361      * all entity relation paths that exist in an IEntityDescriptor-instance and
362      * it's optional belonging DataQuery. EntityRelationPathAliasTree-objects
363      * will represent all paths a tree of uniq aliases. It is used to make a
364      * SQL-select-statement with as few as possible used uniq aliases.  
365      * 
366      * @author Niklas Norberg
367      */
368     private class EntityRelationPathAliasTree
369     {
370         private HashMap mEntityRelationPathMap_With_Uniq_Aliases = new HashMap();
371         
372         private Map getUniqAliasMap()
373         {
374             return mEntityRelationPathMap_With_Uniq_Aliases;
375         }
376         
377         /*** Get uniq aliases for a path.
378          * 
379          * @param entityRelationPath a path to get uniq aliases for.
380          * @return a String-array containing uniq aliases for the path.
381          */
382         private String[] getAliasArray(IEntityRelationPath entityRelationPath)
383         {
384             return (String[])mEntityRelationPathMap_With_Uniq_Aliases.get(entityRelationPath);
385         }
386         
387         /*** Make uniq aliases for a path and ADDS the path and the aliases to the
388          * Map.
389          * 
390          * @param path2MakeAliasFor the path to make aliases for.
391          * @param aliasArray2AddAliases2 uniq aliases are added to this array.
392          */
393         private void makeUniqAliasForPath(IEntityRelationPath path2MakeAliasFor, String[] aliasArray2AddAliases2)
394         {
395             // Observe that as code is written startCopyAliasFromIndex
396             // must and always are >= 1!
397             int startCopyAliasFromIndex = 1;
398             
399             //Try to copy aliases from existing uniq arrays
400             Iterator it = mEntityRelationPathMap_With_Uniq_Aliases.keySet().iterator();
401             while(it.hasNext())
402             {
403                 IEntityRelationPath path2CopyAliasFrom = (IEntityRelationPath)it.next();
404                 startCopyAliasFromIndex = this.copyAlias( path2MakeAliasFor, aliasArray2AddAliases2, path2CopyAliasFrom, startCopyAliasFromIndex);
405                 if (startCopyAliasFromIndex==aliasArray2AddAliases2.length)
406                     break;
407             }
408             
409             //Make new aliases for the rest (if any) of the array
410             if (startCopyAliasFromIndex<aliasArray2AddAliases2.length)
411             {
412                 // This variable (entityDescriptor2MakeAliasFor) is the
413                 // IEntityDescriptor (along the path) that either already has an 
414                 // uniq alias or shall get a new uniq alias:
415                 IEntityDescriptor entityDescriptor2MakeAliasFor = path2MakeAliasFor.getFirstEntityDescriptor();
416                 
417                 //Walk path (iterate over entity descriptors) to the one that had the last set alias
418                 for (int relationIndex = 0; relationIndex < startCopyAliasFromIndex-1; relationIndex++)
419                     entityDescriptor2MakeAliasFor = path2MakeAliasFor.getRelation(relationIndex).getRelatedEntityDescriptor(entityDescriptor2MakeAliasFor);
420                 
421                 //Get and set aliases for the rest of the array
422                 for (int aliasIndex = startCopyAliasFromIndex; aliasIndex < aliasArray2AddAliases2.length; aliasIndex++)
423                 {//aliasIndex>=1
424                     int relationIndex = aliasIndex-1;
425                     entityDescriptor2MakeAliasFor = path2MakeAliasFor.getRelation(relationIndex).getRelatedEntityDescriptor(entityDescriptor2MakeAliasFor);
426                     
427                     if(aliasArray2AddAliases2[aliasIndex]!=null)
428                     {//alias was set earlier
429                         //check that alias is marked as occupied
430                         if( !SQLSelectCommand.this.isAliasTaken(aliasArray2AddAliases2[aliasIndex]) )
431                             throw new RuntimeException("The alias: "+
432                                     aliasArray2AddAliases2[aliasIndex]+
433                                     " should been marked uniq but isn't!");
434                     }
435                     else
436                     {      
437                         //Get an uniq alias:
438                         String uniqAlias = SQLSelectCommand.this.getAvailabeAlias(entityDescriptor2MakeAliasFor.getCodeName());
439                         //than mark alias as uniq
440                         SQLSelectCommand.this.markAliasAsUniq(uniqAlias);
441                         //and use it!
442                         aliasArray2AddAliases2[aliasIndex] = uniqAlias;
443                     }
444                 }
445             }
446             
447             //put the now complete alias array in the map
448             mEntityRelationPathMap_With_Uniq_Aliases.put(path2MakeAliasFor, aliasArray2AddAliases2);
449         }
450         
451         /*** Help method that is used to copy uniq alises.
452          * It follows another path as long as the road is same and copy over
453          * aliases. Copying are only made if needed, if aliases are set already
454          * it checks that they are the same in both arrays!
455          * 
456          * @param path2MakeAliasFor path to make aliases for.
457          * @param aliasArray2AddAliases2 alias array to copy uniq aliases to.
458          * @param path2CopyAliasFrom path to follow.
459          * @param startingFromIndex index to start copying from.
460          * @return an int representing the index for the next alias to set,
461          * if all aliases are done this integer will equal the length of the
462          * parameter aliasArray2AddAliases2.
463          */
464         private int copyAlias(IEntityRelationPath path2MakeAliasFor,
465                                 String[] aliasArray2AddAliases2,
466                                 IEntityRelationPath path2CopyAliasFrom,
467                                 int startCopyFromIndex)
468         {
469             if(startCopyFromIndex>path2CopyAliasFrom.getRelationCount()+1)
470             {
471                 // The path path2CopyAliasFrom ain't long enough.
472                 return startCopyFromIndex;
473             }
474             
475             String[] uniqAliases = this.getAliasArray(path2CopyAliasFrom);
476             
477             int pathIndex = 0;
478             int aliasIndex = 1;
479             
480             while( aliasIndex < aliasArray2AddAliases2.length)
481             {
482                 if(pathIndex==path2CopyAliasFrom.getRelationCount())
483                 {//We have followed the path: path2CopyAliasFrom beyond the end.
484                     break;
485                 }
486                 
487                 if( path2MakeAliasFor.getRelation(pathIndex) == path2CopyAliasFrom.getRelation(pathIndex) &&
488                         path2MakeAliasFor.isRelationByReference(pathIndex) == path2CopyAliasFrom.isRelationByReference(pathIndex))
489                 {//The paths follow the same road so far.
490                     if(aliasIndex<startCopyFromIndex || aliasArray2AddAliases2[aliasIndex]!=null)
491                     {
492                         //DEBUG just double-check that alias is same!
493                         if( !aliasArray2AddAliases2[aliasIndex].equals(uniqAliases[aliasIndex]) )
494                             throw new RuntimeException("WHOOPS ALIAS SHOULD BE EQUAL FOR PATH'S FOLLOWING THE SAME PATH!");
495                     }
496                     else
497                     {
498                         //copy the alias-String only if the place to copy to is null
499                         aliasArray2AddAliases2[aliasIndex] = uniqAliases[aliasIndex];
500                     }
501                     //We have checked/copied the alias let's get the next one!
502                     pathIndex++;
503                     aliasIndex++;
504                     continue;//continue to follow path in path2CompareAliasWith
505                 }
506                 else
507                 {//Paths take different ways from here on.
508                     break;
509                 }
510             }
511             
512             return aliasIndex>startCopyFromIndex?aliasIndex:startCopyFromIndex;
513         }
514         
515         /*** Debug-method that prints all IEntityRelationPaths and the beloning
516          * String-array that are contained in the parameter entityRelationPathMap
517          */
518         private void printAllPathsAndBelongingArray(Map entityRelationPathMap)
519         {
520             Iterator it = entityRelationPathMap.keySet().iterator();
521             if (it.hasNext())
522             {
523                 System.out.println("\n\n");
524                 while(it.hasNext())
525                 {
526                     IEntityRelationPath key = (IEntityRelationPath)it.next();
527                     System.out.println(key);
528                     String[] aliases = (String[])entityRelationPathMap.get(key);
529                     int i = 0;
530                     while (i < aliases.length-1)
531                         System.out.print(aliases[i++]+", ");
532                     System.out.println(aliases[i]);
533                 }
534                 System.out.println("\n\n");
535             }
536         }  
537         
538     }
539     
540      
541     
542  }