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  
19  package org.caleigo.core;
20  
21  
22  import java.util.*;
23  
24  import org.caleigo.core.exception.*;
25  import org.caleigo.toolkit.util.*;
26  
27  /*** <Description for EntityRelationPath>
28   *
29   * @author  Dennis Zikovic
30   * @version 1.00
31   *
32   *//* 
33   *
34   * WHEN        WHO               WHY & WHAT
35   * -----------------------------------------------------------------------------
36   * 2001-11-28  Dennis Zikovic    Creation
37   */
38  public class EntityRelationPath implements IEntityRelationPath
39  {
40      // Static methods ----------------------------------------------------------
41      
42      /*** Factory method for creating an EntityRelationPath from a string that has
43       * been returned by <code>convertToString</code>.
44       * 
45       * @param entityDescriptor     the start entity descriptor.
46       * @param configurationString  the string that should be used to create the relation.
47       */
48      public static EntityRelationPath create(IEntityDescriptor startEntityDescriptor,
49                                              String configurationString)
50      {
51          if (configurationString == null ||
52              configurationString.length() == 0)
53              return new EntityRelationPath(startEntityDescriptor);
54          
55          EntityRelationPath entityRelationPath = null;
56          int startIndex = 0;
57          int endIndex = configurationString.indexOf('.');
58          int colonIndex = 0;
59          while (endIndex != -1)
60          {
61              colonIndex = configurationString.indexOf(':', startIndex);
62              entityRelationPath = appendRelation(
63                      startEntityDescriptor,
64                      entityRelationPath,
65                      configurationString.substring(startIndex, colonIndex),
66                      Boolean.valueOf(configurationString.substring(colonIndex + 1, endIndex)).booleanValue());
67              
68              if (entityRelationPath == null)
69                  throw new IllegalArgumentException("Illegal format of configuration string");
70                  
71              startIndex = endIndex + 1;
72              endIndex = configurationString.indexOf('.', startIndex);
73          }
74          
75          colonIndex = configurationString.indexOf(':', startIndex);
76          if (colonIndex == -1)
77              throw new IllegalArgumentException("Illegal format of configuration string");
78          
79          entityRelationPath = appendRelation(
80                  startEntityDescriptor,
81                  entityRelationPath,
82                  configurationString.substring(startIndex, colonIndex),
83                  Boolean.valueOf(configurationString.substring(colonIndex + 1, configurationString.length())).booleanValue());
84          
85          return entityRelationPath;
86      }
87      
88      private static EntityRelationPath appendRelation(IEntityDescriptor entityDescriptor,
89                                                       EntityRelationPath relationPath,
90                                                       String relationCodeName, boolean byReference)
91      {
92          if (relationPath == null)
93          {
94              int relationIndex = entityDescriptor.getEntityRelationIndex(relationCodeName);
95              if (relationIndex == -1)
96                  return null;
97  
98              relationPath = new EntityRelationPath(entityDescriptor.getEntityRelation(relationIndex), byReference);
99          }
100         else
101         {
102             IEntityDescriptor lastEntityDescriptor = relationPath.getLastEntityDescriptor();
103             int relationIndex = lastEntityDescriptor.getEntityRelationIndex(relationCodeName);
104             if (relationIndex == -1)
105                 return null;
106 
107             relationPath.appendRelation(lastEntityDescriptor.getEntityRelation(relationIndex), byReference);
108         }
109         
110         return relationPath;
111     }
112     
113     // Data members ------------------------------------------------------------
114     private IEntityDescriptor mFirstEntityDescriptor;
115     private IEntityDescriptor mLastEntityDescriptor;
116     private IEntityRelation[] mRelations;
117     private boolean[] mReferenceRelationFlags;
118     
119     // Constructors ------------------------------------------------------------
120     
121     /*** Static help method that creates a new path between the submited entity
122      * descriptors. The method will try to make a "smart" descision if there are
123      * multiple possible paths between the descriptors. No guranties can made
124      * however about the returned path except that the same path allways will 
125      * be returned for the same entity descriptors. If no path exists between 
126      * the two entities then null is returned. 
127      */
128     public static IEntityRelationPath create(IEntityDescriptor startEntityDescriptor, IEntityDescriptor endEntityDescriptor) 
129     {
130         return create(startEntityDescriptor, (IFieldDescriptor[])null, endEntityDescriptor);
131     }
132     
133     /*** Static help method that creates a new path between the submited entity
134      * descriptors. The method will try to make a "smart" descision if there are
135      * multiple possible paths between the descriptors. No guranties can made
136      * however about the returned path except that the same path allways will 
137      * be returned for the same entity descriptors. If no path exists between 
138      * the two entities then null is returned. <p>
139      * The fieldWayPoint parameter can be used to guide the method in choosing
140      * a specific path.
141      */
142     public static IEntityRelationPath create(IEntityDescriptor startEntityDescriptor, IFieldDescriptor fieldWayPoint, IEntityDescriptor endEntityDescriptor) 
143     {
144         return create(startEntityDescriptor, new IFieldDescriptor[] {fieldWayPoint}, endEntityDescriptor);
145     }
146     
147     /*** Static help method that creates a new path between the submited entity
148      * descriptors. The method will try to make a "smart" descision if there are
149      * multiple possible paths between the descriptors. No guranties can made
150      * however about the returned path except that the same path allways will 
151      * be returned for the same entity descriptors. If no path exists between 
152      * the two entities then null is returned. <p>
153      * The fieldWayPoint parameter can be used to guide the method in choosing
154      * a specific path.
155      */
156     public static IEntityRelationPath create(IEntityDescriptor startEntityDescriptor, IFieldDescriptor[] fieldWayPoints, IEntityDescriptor endEntityDescriptor) 
157     {
158         PathFinderVisitor finder = new PathFinderVisitor(startEntityDescriptor, endEntityDescriptor, fieldWayPoints);
159         if(finder.getBestRelationPath()==null)
160             throw new InvalidRelationException("No relation path exists between the descriptors "+startEntityDescriptor+" and "+endEntityDescriptor);
161         return finder.getBestRelationPath();
162     }
163     
164     // Constructors ------------------------------------------------------------
165     
166     /*** Creates new EntityRelationPath. 
167      */
168     public EntityRelationPath(IEntityRelation relation, boolean byReference)
169     {
170         mRelations = new IEntityRelation[] {relation};
171         mReferenceRelationFlags = new boolean[] {byReference};
172         
173         if(mReferenceRelationFlags[0])
174             mFirstEntityDescriptor =  mRelations[0].getReferenceEntityDescriptor();
175         else
176             mFirstEntityDescriptor = mRelations[0].getTargetEntityDescriptor();
177         
178         // Note that if the last relation is a reference relation then the
179         // reference target is the paths end.
180         if(mReferenceRelationFlags[0])
181             mLastEntityDescriptor =  mRelations[0].getTargetEntityDescriptor();
182         else
183             mLastEntityDescriptor =  mRelations[0].getReferenceEntityDescriptor();
184     }
185     
186     /*** Creates new EntityRelationPath as a copy of another IRelationPath. 
187      */
188     public EntityRelationPath(IEntityRelationPath relationPath)
189     {
190         if(relationPath.getRelationCount()>0)
191         {
192             mRelations = new IEntityRelation[relationPath.getRelationCount()];
193             mReferenceRelationFlags = new boolean[relationPath.getRelationCount()];
194             for(int j=0; j<relationPath.getRelationCount(); j++)
195             {
196                 mRelations[j] = relationPath.getRelation(j);
197                 mReferenceRelationFlags[j] = relationPath.isRelationByReference(j);
198             }
199         }
200         mFirstEntityDescriptor = relationPath.getFirstEntityDescriptor();
201         mLastEntityDescriptor = relationPath.getLastEntityDescriptor();
202     }
203     
204     /*** Creates new EntityRelationPath as a "null" path. This is usable to 
205      * define "no path" between two entities of the same descriptor wich is
206      * necessary to differentiate against a path an none empty path.
207      */
208     public EntityRelationPath(IEntityDescriptor descriptor)
209     {
210         mFirstEntityDescriptor = descriptor;
211         mLastEntityDescriptor = descriptor;
212     }
213     
214     // Superclass overrides ----------------------------------------------------
215     
216     public int hashCode()
217     {
218         int code = 0;
219         for(int j=0; j<this.getRelationCount(); j++)
220             if(this.isRelationByReference(j))
221                 code += this.getRelation(j).hashCode();
222             else
223                 code -= this.getRelation(j).hashCode();
224         return code;
225     }
226     
227     public boolean equals(Object obj)
228     {
229         if(obj==null || !(obj instanceof IEntityRelationPath))
230             return false;
231         
232         IEntityRelationPath otherPath = (IEntityRelationPath)obj;
233         if(otherPath.getRelationCount()!=this.getRelationCount())
234             return false;
235         
236         for(int j=0; j<this.getRelationCount(); j++)
237             if(!this.getRelation(j).equals(otherPath.getRelation(j)) || this.isRelationByReference(j)!=this.isRelationByReference(j))
238                 return false;
239         
240         return true;
241     }
242     
243     // IEntityRelationPath implementation --------------------------------------
244     
245     /*** Returns the IEntityRelation object that marks is the start point 
246      * of the relation path.
247      */ 
248     public IEntityRelation getFirstRelation()
249     {
250         return mRelations[0];
251     }
252     
253     /*** Returns true if the start relation starts on the reference side.
254      * False would mean that the relation starts on the target side.
255      */
256     public boolean isFirstRelationByReference()
257     {
258         return mReferenceRelationFlags[0];
259     }
260     
261     /*** Returns the IEntityDescriptor object that marks is the start point 
262      * of the relation path.
263      */ 
264     public IEntityDescriptor getFirstEntityDescriptor()
265     {
266         return mFirstEntityDescriptor;
267     }
268     
269     /*** Returns the IFieldDescriptor objects that marks is the start point 
270      * of the relation path. The descriptors are returned as an Iterator.
271      */ 
272     public java.util.Iterator getFirstFieldDescriptors()
273     {
274         if(mReferenceRelationFlags[0])
275             return mRelations[0].getReferenceFieldDescriptors();
276         else
277             return mRelations[0].getTargetFieldDescriptors();
278     }
279     
280     /*** Returns the IEntityRelation object that marks is the end point 
281      * of the relation path.
282      */ 
283     public IEntityRelation getLastRelation()
284     {
285         return mRelations[mRelations.length-1];
286     }
287     
288     /*** Returns true if the last relation starts on the reference side.
289      * False would mean that the relation ends on the target side.
290      */
291     public boolean isLastRelationByReference()
292     {
293         return mReferenceRelationFlags[mRelations.length-1];
294     }
295     
296     /*** Returns the IEntityDescriptor object that marks is the end point 
297      * of the relation path.
298      */ 
299     public IEntityDescriptor getLastEntityDescriptor()
300     {
301         return mLastEntityDescriptor;
302     }
303     
304     /*** Returns the IFieldDescriptor objects that marks is the end point 
305      * of the relation path. The descriptors are returned as an Iterator.
306      */ 
307     public java.util.Iterator getLastFieldDescriptors()
308     {
309         if(mReferenceRelationFlags[mRelations.length-1])
310             return mRelations[mRelations.length-1].getTargetFieldDescriptors();
311         else
312             return mRelations[mRelations.length-1].getReferenceFieldDescriptors();
313     }
314     
315     /*** Access method that returns the number of IEntityRelation objects
316      * that defines the relation path.
317      */
318     public int getRelationCount()
319     {
320         if(mRelations==null)
321             return 0;
322         else
323             return mRelations.length;
324     }
325     
326     /*** Returns true if the indexed relation is followed in the forward 
327      * directiona long the path by the reference side of the entity relation.
328      * False would mean that the relation is followed by the target side.
329      */
330     public boolean isRelationByReference(int relationIndex)
331     {
332         return mReferenceRelationFlags[relationIndex];
333     }
334     
335     /*** Access method that returns the indexed IEntityRelation object. 
336      * The index is related to the begining/first entity of the path 
337      * meaning that relation zero is the relation that links to first 
338      * entity descriptor in the path.
339      */
340     public IEntityRelation getRelation(int index)
341     {
342         return mRelations[index];
343     }
344     
345     /*** Access method that returns all IEntityRelation objects in the path
346      * in the form of an Iterator.
347      */
348     public java.util.Iterator getRelations()
349     {
350         return Iterators.iterate(mRelations);
351     }
352     
353     /*** Access method that returns the cardinality for the IEntityDescriptor
354      * object at the begining of the relation path. The returned value is one
355      * of the constants ONE or MANY that signifies the number of entities a 
356      * single entity on the end-side of the relation path can refer to. 
357      */
358     public int getForwardCardinality()
359     {
360         boolean isAllReference = true;
361         for(int j=0; mRelations!=null && j<mRelations.length && isAllReference; j++)
362             isAllReference = mReferenceRelationFlags[j];
363         if(isAllReference)
364             return ONE;
365         else
366             return MANY;        
367     }
368     
369     /*** Access method that returns the cardinality for the IEntityDescriptor
370      * object at the end of the relation path. The returned value is one
371      * of the constants ONE or MANY that signifies the number of entities a 
372      * single entity on the start-side of the relation path can refer to. 
373      */
374     public int getReverseCardinality()
375     {
376         boolean isAllTarget = true;
377         for(int j=0; mRelations!=null && j<mRelations.length && isAllTarget; j++)
378             isAllTarget = !mReferenceRelationFlags[j];
379         if(isAllTarget)
380             return ONE;
381         else
382             return MANY;        
383     }
384     
385     /*** Returns a Qualifier that satisfies all the field requirements that 
386      * have to be satisfied to define a relation between the first and last 
387      * entity descriptor of the relation path.
388      */
389     public Qualifier getRelationQualifier()
390     {
391         if(this.getRelationCount()==0)
392             return null;
393         
394         Qualifier qualifier = this.getRelation(0).getRelationQualifier(); 
395         for(int j=1; j<this.getRelationCount(); j++)
396             qualifier = qualifier.and(this.getRelation(j).getRelationQualifier());   
397         return qualifier;
398     }
399     
400     /*** This help method returns true if the provided entity descriptors are
401      * related "along" the called relation path. The order of the descriptors
402      * are irelevant. The provided entity descriptors does not have to be the 
403      * same as the end node descriptors to be related along the path. but they 
404      * must atleast partially contain the field descriptors that does define
405      * the start and connections for the path at the end-nodes. 
406      * <p>
407      * Note that the descriptors are are only considered to be related "along 
408      * the path" if they satisfies the end-node field requriments for 
409      * relationship. In other words the entire path must "followed" for this
410      * method to consider the descriptors as related.
411      */
412     public boolean isRelatedByPath(IEntityDescriptor descriptor1, IEntityDescriptor descriptor2)
413     {
414         return (Relations.isIdentityIntersection(this.getFirstEntityDescriptor(), descriptor1) && Relations.isIdentityIntersection(this.getLastEntityDescriptor(), descriptor2))
415                 || (Relations.isIdentityIntersection(this.getFirstEntityDescriptor(), descriptor2) && Relations.isIdentityIntersection(this.getLastEntityDescriptor(), descriptor1));
416 //        return (this.getFirstRelation().canBeReference(descriptor1) && this.getLastRelation().canBeTarget(descriptor2)) ||
417 //                (this.getFirstRelation().canBeReference(descriptor2) && this.getLastRelation().canBeTarget(descriptor1));
418     }
419     
420     /*** Returns a string representation of this IEntirtyRelationPath that can
421      * be used to restore this IEntirtyRelationPath. It is recommended that
422      * implementing classes declares a static factory method that takes this string
423      * as a parameter.
424      */
425     public String convertToString()
426     {
427         StringBuffer stringBuffer = new StringBuffer();
428         for (int i = 0; i < this.getRelationCount(); i++)
429         {
430             if (stringBuffer.length() > 0)
431                 stringBuffer.append('.');
432                 
433             stringBuffer.append(this.getRelation(i).getCodeName());
434             stringBuffer.append(':');
435             stringBuffer.append(this.isRelationByReference(i));
436         }
437         
438         return stringBuffer.toString();
439     }
440     
441     /*** Access method that returns the cardinality for the IEntityDescriptor
442      * object at the end of the relation path. The returned value is one
443      * of the constants ONE or MANY that signifies the number of entities a 
444      * single entity on the start-side of the relation path can refer to. 
445      * Note that the order is relevant since the cardinality is defined from
446      * start to the end descriptor.
447      */
448 /*    public int getCardinalityByPath(IEntityDescriptor startDescriptor, IEntityDescriptor endDescriptor)
449     {
450         // Validate relation.
451         if(!this.isRelatedByPath(startDescriptor, endDescriptor))
452             throw new InvalidRelationException("The relation path is not valid for the descriptors "+startDescriptor+" and "+endDescriptor);
453         
454         // Calculate cardinality
455         IEntityRelation firstRelation = (IEntityRelation)mRelationList.get(0);
456         IEntityRelation lastRelation = (IEntityRelation)mRelationList.get(mRelationList.size()-1);
457         if(firstRelation.canBeFullReference(startDescriptor) && lastRelation.canBeTargeted(endDescriptor))
458             return this.getForwardCardinality();
459         else if(lastRelation.canBeFullReference(startDescriptor) && firstRelation.canBeTargeted(endDescriptor))
460             return this.getReverseCardinality();
461         else
462             return MANY;
463     }
464 */
465 
466     // Access methods ----------------------------------------------------------
467     
468     public void appendRelation(IEntityRelation relation)
469     {
470         this.appendRelation(relation, true);
471     }
472     
473     public void appendRelation(IEntityRelation relation, boolean byReference)
474     {
475         // Set relation path if current relation path was empty.
476         if(mRelations==null)
477         {
478             mRelations = new IEntityRelation[] {relation};
479             mReferenceRelationFlags = new boolean[] {byReference};
480         }
481         else 
482         {
483             // Validate new relation and break if not valid.
484             if(byReference && relation.getReferenceEntityDescriptor()!=this.getLastEntityDescriptor()
485                     || !byReference && relation.getTargetEntityDescriptor()!=this.getLastEntityDescriptor())
486                 throw new InvalidRelationException("Invalid relation path extension of "+this+" with "+(byReference ? "forward " : "reversed ")+relation);
487             
488             // Extend existing path.
489             IEntityRelation[] relations = new IEntityRelation[mRelations.length+1];
490             boolean[] relationFlags = new boolean[mRelations.length+1];
491             System.arraycopy(mRelations, 0, relations, 0, mRelations.length);
492             System.arraycopy(mReferenceRelationFlags, 0, relationFlags, 0, mRelations.length);
493             relations[mRelations.length] = relation;
494             relationFlags[mRelations.length] = byReference;
495             mRelations = relations;
496             mReferenceRelationFlags = relationFlags;
497         }
498             
499         // Note that if the last relation is a reference relation then the
500         // reference target is the paths end.
501         if(mReferenceRelationFlags[mRelations.length-1])
502             mLastEntityDescriptor =  mRelations[mRelations.length-1].getTargetEntityDescriptor();
503         else
504             mLastEntityDescriptor =  mRelations[mRelations.length-1].getReferenceEntityDescriptor();
505     }
506     
507     public void appendRelationPath(IEntityRelationPath path)
508     {
509         // Validate new relation and break if not valid.
510         if(this.getLastEntityDescriptor()!=path.getFirstEntityDescriptor())
511             throw new InvalidRelationException("Invalid relation path extension of "+this+" with "+path);
512 
513         // Extend existing path.
514         int offset = 0;
515         IEntityRelation[] relations;
516         boolean[] relationFlags;
517         if(mRelations==null)
518         {
519             relations = new IEntityRelation[path.getRelationCount()];
520             relationFlags = new boolean[path.getRelationCount()];
521         }
522         else
523         {
524             relations = new IEntityRelation[mRelations.length+path.getRelationCount()];
525             relationFlags = new boolean[mRelations.length+path.getRelationCount()];
526             System.arraycopy(mRelations, 0, relations, 0, mRelations.length);
527             System.arraycopy(mReferenceRelationFlags, 0, relationFlags, 0, mRelations.length);
528             offset = mRelations.length;
529         }
530         for(int j=0; j<path.getRelationCount(); j++)
531         {
532             relations[offset+j] = path.getRelation(j);
533             relationFlags[offset+j] = path.isRelationByReference(j);
534         }
535         mRelations = relations;
536         mReferenceRelationFlags = relationFlags;
537             
538         // Note that if the last relation is a reference relation then the
539         // reference target is the paths end.
540         if(mReferenceRelationFlags[mRelations.length-1])
541             mLastEntityDescriptor =  mRelations[mRelations.length-1].getTargetEntityDescriptor();
542         else
543             mLastEntityDescriptor =  mRelations[mRelations.length-1].getReferenceEntityDescriptor();
544     }
545     
546     // Help methods ------------------------------------------------------------
547     
548     public String toString()
549     {
550         String path = "EntityRelationPath [";
551         for(int j=0; j<mRelations.length; j++)
552         {
553             if(mReferenceRelationFlags[j])
554                 path += mRelations[j].getReferenceEntityDescriptor()+"-->";
555             else
556                 path += mRelations[j].getTargetEntityDescriptor()+"<--";
557         }
558         if(mReferenceRelationFlags[mRelations.length-1])
559             path += mRelations[mRelations.length-1].getTargetEntityDescriptor()+"]";
560         else
561             path += mRelations[mRelations.length-1].getReferenceEntityDescriptor()+"]";
562         return path;
563     }
564     
565     // Nested classes ----------------------------------------------------------
566     protected static class PathFinderVisitor
567     {
568         // Data members --------------------------------------------------------
569         protected IEntityDescriptor mStartDescriptor;
570         protected IEntityDescriptor mTargetDescriptor;
571         protected IFieldDescriptor[] mFieldWayPoints;
572         
573         protected Map mBestRankMap;
574         
575         // Constructors --------------------------------------------------------
576         public PathFinderVisitor(IEntityDescriptor startEntity, IEntityDescriptor endEntity, IFieldDescriptor[] fieldWayPoints)
577         {
578             mStartDescriptor = startEntity;
579             mTargetDescriptor = endEntity;
580             mFieldWayPoints = fieldWayPoints;
581 
582             mBestRankMap = new HashMap(200);
583             mBestRankMap.put(startEntity, new RankedPath(new EntityRelationPath(startEntity), 200)); // Define empty relation to self.
584             
585             // Commence the recursive relation scan.
586             this.findBestPath(startEntity, new RankedPath(new EntityRelationPath(startEntity), 0));
587         }
588                 
589         // Access methods ------------------------------------------------------
590         public IEntityRelationPath getBestRelationPath()
591         {
592             if(mBestRankMap.get(mTargetDescriptor)!=null)
593                 return ((RankedPath)mBestRankMap.get(mTargetDescriptor)).getRelationPath();
594             else
595                 return null;
596         }
597         
598         // Help methods --------------------------------------------------------
599         protected void findBestPath(IEntityDescriptor entity, RankedPath entityPath)
600         {
601             // Get relatin list for the entity descriptor.
602             Iterator relationIterator = entity.getEntityRelations();
603             
604             // Make a list of ranked relation path for all relations in the
605             // targeted entity descriptor.
606             List rankedList = new ArrayList();
607             IEntityRelation relation = null;      
608             while(relationIterator.hasNext())
609             {
610                 relation = (IEntityRelation)relationIterator.next();
611                 if(relation.getRelatedEntityDescriptor(entity)==entity)
612                 {
613                     rankedList.add(entityPath.extendPath(relation, true, this.rankRelation(entity, relation)));
614                     rankedList.add(entityPath.extendPath(relation, false, this.rankRelation(entity, relation)));
615                 }
616                 else
617                     rankedList.add(entityPath.extendPath(relation, (relation.getReferenceEntityDescriptor()==entity), this.rankRelation(entity, relation)));
618             }
619             
620             // Create ranked path list for the best ranked relation to
621             // each unique directly related entity descriptor.
622             for(int j=0; j+1<rankedList.size(); j++)
623             {
624                 RankedPath lowPath = (RankedPath)rankedList.get(j);
625                 ListIterator it = rankedList.listIterator(j+1);
626                 while(it.hasNext())
627                 {
628                     RankedPath highPath = (RankedPath)it.next();
629                     if(lowPath.getLastDescriptor() == highPath.getLastDescriptor())
630                     {
631                         // Remove relation of lowest rank.
632                         if(highPath.getRank()<lowPath.getRank())
633                              rankedList.set(j, highPath);
634                         
635                         // Remove duplicate relation and keep the best.
636                         it.remove();
637                     }
638                 }
639             }
640             
641             // Compare all remaining relation paths to the best stored path
642             // in the cache. If a better path is found stored it as the new 
643             // best path and recurse along that path.
644             for(int j=0; j<rankedList.size(); j++)
645             {
646                 RankedPath bestStoredPath = (RankedPath)mBestRankMap.get(((RankedPath)rankedList.get(j)).getLastDescriptor());
647                 if(bestStoredPath==null || ((RankedPath)rankedList.get(j)).getRank()<bestStoredPath.getRank())
648                 {
649                     mBestRankMap.put(((RankedPath)rankedList.get(j)).getLastDescriptor(), (RankedPath)rankedList.get(j));
650                     this.findBestPath(((RankedPath)rankedList.get(j)).getLastDescriptor(), (RankedPath)rankedList.get(j));
651                 }
652             }
653         }
654         
655         protected int rankRelation(IEntityDescriptor startEntity, IEntityRelation relation)
656         {
657             // Check for waypoint fields in relation. If a waypoint is part
658             // of the relation then set the rank/"road cost" to zero.
659             for(int j=0; mFieldWayPoints!=null && j<mFieldWayPoints.length; j++)
660                 if(relation.isRelationField(mFieldWayPoints[j]))
661                     return 0;
662             
663             // Base value.
664             int rank = 1000;
665             
666             // Cardinality.
667             if(!relation.canBeFullReference(startEntity))
668                 rank += 600;
669             
670             // Required or no.
671             if(!relation.isRequired())
672                 rank += 600;
673             
674             // Entity descriptor type
675             switch(startEntity.getEntityType())
676             {
677                 case IEntityDescriptor.MASTER_ENTITY: rank += 0; break;
678                 case IEntityDescriptor.SLAVE_ENTITY: rank += 50; break;
679                 case IEntityDescriptor.LINK_ENTITY: rank += 25; break;
680                 case IEntityDescriptor.STATIC_ENTITY: rank += 100; break;
681                 case IEntityDescriptor.CUSTOM_ENTITY: rank += 100; break;
682             }
683             
684             // Field index?
685             
686             return rank;
687         }
688         
689         // Nested classes ------------------------------------------------------
690         protected class RankedPath
691         {
692             private IEntityRelationPath mPath;
693             private int mRank;
694             
695             public RankedPath(IEntityRelationPath path, int rank)
696             {
697                 mPath = path;
698                 mRank = rank;
699             }
700             
701             public RankedPath extendPath(IEntityRelation relation, boolean byReference, int rank)
702             {
703                 // Make copy to avoid mutation conflicts in multiple recursive branches.
704                 EntityRelationPath newPath = new EntityRelationPath(mPath);
705                 newPath.appendRelation(relation, byReference);
706                 return new RankedPath(newPath, mRank+rank);
707             }
708             
709             public IEntityDescriptor getLastDescriptor()
710             {
711                 return mPath.getLastEntityDescriptor();
712             }
713             
714             public IEntityRelationPath getRelationPath()
715             {
716                 return mPath;
717             }
718             
719             public int getRank()
720             {
721                 return mRank;
722             }
723         }
724     }
725 }
726 
727 
728