1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
114 private IEntityDescriptor mFirstEntityDescriptor;
115 private IEntityDescriptor mLastEntityDescriptor;
116 private IEntityRelation[] mRelations;
117 private boolean[] mReferenceRelationFlags;
118
119
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
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
179
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
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
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
417
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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
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
476 if(mRelations==null)
477 {
478 mRelations = new IEntityRelation[] {relation};
479 mReferenceRelationFlags = new boolean[] {byReference};
480 }
481 else
482 {
483
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
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
500
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
510 if(this.getLastEntityDescriptor()!=path.getFirstEntityDescriptor())
511 throw new InvalidRelationException("Invalid relation path extension of "+this+" with "+path);
512
513
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
539
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
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
566 protected static class PathFinderVisitor
567 {
568
569 protected IEntityDescriptor mStartDescriptor;
570 protected IEntityDescriptor mTargetDescriptor;
571 protected IFieldDescriptor[] mFieldWayPoints;
572
573 protected Map mBestRankMap;
574
575
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));
584
585
586 this.findBestPath(startEntity, new RankedPath(new EntityRelationPath(startEntity), 0));
587 }
588
589
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
599 protected void findBestPath(IEntityDescriptor entity, RankedPath entityPath)
600 {
601
602 Iterator relationIterator = entity.getEntityRelations();
603
604
605
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
621
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
632 if(highPath.getRank()<lowPath.getRank())
633 rankedList.set(j, highPath);
634
635
636 it.remove();
637 }
638 }
639 }
640
641
642
643
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
658
659 for(int j=0; mFieldWayPoints!=null && j<mFieldWayPoints.length; j++)
660 if(relation.isRelationField(mFieldWayPoints[j]))
661 return 0;
662
663
664 int rank = 1000;
665
666
667 if(!relation.canBeFullReference(startEntity))
668 rank += 600;
669
670
671 if(!relation.isRequired())
672 rank += 600;
673
674
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
685
686 return rank;
687 }
688
689
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
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