1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.ui.swing;
20
21 import java.awt.Component;
22 import java.lang.ref.Reference;
23 import java.lang.ref.WeakReference;
24 import java.util.Enumeration;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Vector;
29
30 import javax.swing.tree.TreePath;
31
32 import net.wotonomy.control.EODataSource;
33 import net.wotonomy.control.EODelayedObserver;
34 import net.wotonomy.control.EOEditingContext;
35 import net.wotonomy.control.EOObjectStore;
36 import net.wotonomy.control.EOObserverCenter;
37 import net.wotonomy.control.EOQualifier;
38 import net.wotonomy.control.PropertyDataSource;
39 import net.wotonomy.foundation.NSArray;
40 import net.wotonomy.foundation.NSDictionary;
41 import net.wotonomy.foundation.NSMutableDictionary;
42 import net.wotonomy.foundation.NSNotification;
43 import net.wotonomy.foundation.internal.ValueConverter;
44 import net.wotonomy.foundation.internal.WotonomyException;
45 import net.wotonomy.ui.EODisplayGroup;
46 import net.wotonomy.ui.swing.TreeModelAssociation.DelegatingTreeDataSource;
47
48 /***
49 * DisplayGroupNodes are used as nodes in the
50 * TreeModelAssociation's implementation of TreeModel,
51 * and is tightly coupled with TreeModelAssociation
52 * and MasterDetailAssociation. <br><br>
53 *
54 * Even though it is no longer package access,
55 * don't rely on this class because we want to
56 * have the option of completely replacing this
57 * approach in the future.
58 *
59 * @author michael@mpowers.net
60 * @author $Author: cgruber $
61 * @version $Revision: 904 $
62 */
63 abstract public class DisplayGroupNode
64 extends EODisplayGroup
65 {
66 protected TreeModelAssociation parentAssociation;
67 protected EODelayedObserver targetObserver;
68 protected NSMutableDictionary childNodes;
69 protected EODisplayGroup parentGroup;
70 protected Object target;
71 protected boolean isFetched;
72 protected boolean isFetchNeeded;
73 protected boolean useParentOrderings;
74 protected boolean useParentQualifier;
75
76 /***
77 * Constructor for all nodes.
78 * Root node must have a null target.
79 */
80 public DisplayGroupNode(
81 TreeModelAssociation aParentAssociation,
82 EODisplayGroup aParentGroup,
83 Object aTarget )
84 {
85
86
87 parentAssociation = aParentAssociation;
88 target = null;
89 targetObserver = null;
90 parentGroup = aParentGroup;
91 childNodes = new NSMutableDictionary();
92 isFetched = false;
93 isFetchNeeded = false;
94 useParentOrderings = true;
95 useParentQualifier = true;
96
97 EODataSource parentSource = null;
98 if ( parentGroup != null )
99 {
100 parentSource = parentGroup.dataSource();
101 }
102 else
103 if ( parentAssociation.titlesDisplayGroup != null )
104 {
105 parentSource = parentAssociation.titlesDisplayGroup.dataSource();
106 }
107
108
109 if ( aTarget != null )
110 {
111 if ( parentAssociation.childrenKey != null )
112 {
113 if ( parentSource == null )
114 {
115 throw new WotonomyException(
116 "Need a data source when children aspect is bound." );
117 }
118
119 NSArray displayedObjects = parentGroup.displayedObjects();
120 EODataSource childSource = parentSource.dataSourceQualifiedByKey(
121 parentAssociation.childrenKey );
122 childSource.qualifyWithRelationshipKey(
123 parentAssociation.childrenKey, aTarget );
124
125
126 this.setDataSource( childSource );
127
128
129 setTarget( aTarget );
130 }
131 else
132 {
133
134 setTarget( aTarget );
135
136 setDataSource( new PropertyDataSource()
137 {
138 public NSArray fetchObjects()
139 {
140 return new NSArray();
141 }
142 } );
143 }
144 }
145 else
146 {
147
148 if ( parentSource == null )
149 {
150 setDataSource( new PropertyDataSource()
151 {
152 public NSArray fetchObjects()
153 {
154 if ( parentGroup != null )
155 {
156 return parentGroup.displayedObjects();
157 }
158 return null;
159 }
160 } );
161 }
162 else
163 {
164
165 setDataSource( parentSource );
166 }
167 }
168 }
169
170 /***
171 * Overridden to unregister as an editor of the editing context,
172 * since we don't directly present a user interface.
173 */
174 public void setDataSource ( EODataSource aDataSource )
175 {
176 super.setDataSource( aDataSource );
177 if ( ( aDataSource != null )
178 && ( aDataSource.editingContext() != null ) )
179 {
180 aDataSource.editingContext().removeEditor( this );
181 }
182 }
183
184 /***
185 * Returns whether the node should call fetch().
186 */
187 protected boolean isFetched()
188 {
189 if ( isFetchNeeded() )
190 {
191 setFetchNeeded( false );
192 fetch();
193 }
194 return isFetched;
195 }
196
197 /***
198 * Sets whether the node should call fetch().
199 */
200 protected void setFetched( boolean fetched )
201 {
202
203
204 isFetched = fetched;
205 }
206
207 /***
208 * Returns whether the node is in need of a refetch.
209 */
210 protected boolean isFetchNeeded()
211 {
212 return isFetchNeeded;
213 }
214
215 /***
216 * Returns whether the node should call fetch().
217 */
218 protected void setFetchNeeded( boolean fetchNeeded )
219 {
220
221
222 isFetchNeeded = fetchNeeded;
223 }
224
225 /***
226 * Subclasses should override this method to fire an appropriate insertion event.
227 */
228 protected void fireNodesInserted( Object[] path, int[] indexes, Object[] objects )
229 {
230
231 parentAssociation.fireTreeNodesInserted(
232 this, path, indexes, objects );
233 }
234
235 /***
236 * Subclasses should override this method to fire an appropriate change event.
237 */
238 protected void fireNodesChanged( Object[] path, int[] indexes, Object[] objects )
239 {
240
241 parentAssociation.fireTreeNodesChanged(
242 this, path, indexes, objects );
243 }
244
245 /***
246 * Subclasses should override this method to fire an appropriate deletion event.
247 */
248 protected void fireNodesRemoved( Object[] path, int[] indexes, Object[] objects )
249 {
250
251 parentAssociation.fireTreeNodesRemoved(
252 this, path, indexes, objects );
253 }
254
255 /***
256 * Subclasses should override this method to fire an appropriate event.
257 */
258 protected void fireStructureChanged( Object[] path, int[] indexes, Object[] objects )
259 {
260 parentAssociation.fireTreeStructureChanged(
261 this, path, indexes, objects );
262 }
263
264 /***
265 * Overridden to broadcast a tree event after super executes.
266 */
267 public void insertObjectAtIndex ( Object anObject, int anIndex )
268 {
269 int count = getChildCount();
270 if ( target == null )
271 {
272
273
274 EODataSource dataSource = parentGroup.dataSource();
275 if ( dataSource instanceof DelegatingTreeDataSource )
276 {
277 parentGroup.setDataSource(
278 ((DelegatingTreeDataSource)dataSource).delegateDataSource );
279 }
280 parentGroup.insertObjectAtIndex( anObject, anIndex );
281 if ( dataSource instanceof DelegatingTreeDataSource )
282 {
283 parentGroup.setDataSource( dataSource );
284 }
285 return;
286 }
287 else
288 {
289 super.insertObjectAtIndex( anObject, anIndex );
290 }
291 }
292
293 /***
294 * Overridden to broadcast a tree event after super executes.
295 */
296 public boolean deleteObjectAtIndex ( int anIndex )
297 {
298 boolean result;
299 Object node = getChildNodeAt( anIndex );
300 if ( target == null )
301 {
302
303 result = parentGroup.deleteObjectAtIndex( anIndex );
304 }
305 else
306 {
307 result = super.deleteObjectAtIndex( anIndex );
308 }
309
310 return result;
311 }
312
313 /***
314 * Returns the child node that corresponds to the
315 * specified index, creating it if necessary.
316 * The index must be within bounds or an exception
317 * is thrown.
318 */
319 public DisplayGroupNode getChildNodeAt( int anIndex )
320 {
321 boolean wasFetched = isFetched();
322 if ( ! wasFetched ) fetch();
323 Object o = displayedObjects.objectAtIndex( anIndex );
324 DisplayGroupNode result = getChildNodeForObject( o );
325 if ( result == null )
326 {
327 result = createChildNodeForObject( o );
328 }
329 return result;
330 }
331
332 /***
333 * Returns a child node that corresponds to the
334 * specified object, returning null if not found.
335 */
336 protected DisplayGroupNode getChildNodeForObject( Object anObject )
337 {
338 return (DisplayGroupNode)
339 childNodes.objectForKey( new ReferenceKey( anObject ) );
340 }
341
342 /***
343 * Creates a child node that corresponds to the
344 * specified object.
345 */
346 private DisplayGroupNode createChildNodeForObject( Object anObject )
347 {
348 DisplayGroupNode result = parentAssociation.createNode( this, anObject );
349 childNodes.setObjectForKey( result, new ReferenceKey( anObject ) );
350 return result;
351 }
352
353 /***
354 * Returns a tree path of all DisplayGroupNodes leading
355 * to this node, including the root node (but excluding the
356 * titles display group).
357 */
358 public TreePath treePath()
359 {
360 List path = new LinkedList();
361 EODisplayGroup node = this;
362 while ( node instanceof DisplayGroupNode )
363 {
364
365 path.add( 0, node );
366 node = ((DisplayGroupNode)node).parentGroup;
367 }
368 return new TreePath( path.toArray() );
369 }
370
371 /***
372 * Overridden to return the parent group's
373 * sort ordering if useParentOrderings is true.
374 * useParentOrderings is true by default.
375 */
376 public NSArray sortOrderings()
377 {
378 if ( ( useParentOrderings )
379 && ( parentGroup != null ) )
380 {
381 return parentGroup.sortOrderings();
382 }
383 return super.sortOrderings();
384 }
385
386 /***
387 * Overridden to set useParentOrderings to false,
388 * or true if aList is null.
389 */
390 public void setSortOrderings ( List aList )
391 {
392 if ( aList == null )
393 {
394 useParentOrderings = true;
395 }
396 else
397 {
398 useParentOrderings = false;
399 super.setSortOrderings( aList );
400 }
401 }
402
403 /***
404 * Overridden to return the parent group's
405 * qualifier if useParentQualifier is true.
406 * useParentQualifier is true by default.
407 */
408 public EOQualifier qualifier()
409 {
410 if ( ( useParentQualifier )
411 && ( parentGroup != null ) )
412 {
413 return parentGroup.qualifier();
414 }
415 return super.qualifier();
416 }
417
418 /***
419 * Overridden to set useParentQualifier to false,
420 * or true if aList is null.
421 */
422 public void setQualifier ( EOQualifier aQualifier )
423 {
424 if ( aQualifier == null )
425 {
426 useParentQualifier = true;
427 }
428 else
429 {
430 useParentQualifier = false;
431 super.setQualifier( aQualifier );
432 }
433 }
434
435 /***
436 * Overridden to set isFetched to true.
437 */
438 public boolean fetch()
439 {
440
441
442
443
444
445
446 setFetched( true );
447
448
449 if ( target == null ) return true;
450
451
452 dataSource().qualifyWithRelationshipKey(
453 parentAssociation.childrenKey, target );
454
455
456 return super.fetch();
457
458
459
460
461 }
462
463 /***
464 * Returns the object at the appropriate index
465 * in the parent display group.
466 */
467 public Object object()
468 {
469
470 if ( target == null )
471 {
472 return parentAssociation.rootLabel();
473 }
474 return target;
475 }
476
477 /***
478 * Returns the string value of the title property
479 * on the object in the parent display group corresponding
480 * to this index. The tree renderer asks JTrees to
481 * call this method to retrieve a value for display.
482 */
483 public String toString()
484 {
485 Object result = getUserObject();
486 if ( result == null ) result = "[null]";
487 return result.toString();
488 }
489
490
491
492 public int getChildCount()
493 {
494 if ( ! isFetched() ) fetch();
495
496
497
498
499
500 return displayedObjects.count();
501 }
502
503 public int getIndex(DisplayGroupNode node)
504 {
505 if ( ! isFetched() ) fetch();
506 return displayedObjects.indexOfObject(
507 ((DisplayGroupNode)node).target );
508 }
509
510 public boolean getAllowsChildren()
511 {
512 return true;
513 }
514
515 public boolean isLeaf()
516 {
517
518 if ( ( target != null )
519 && ( parentGroup != null )
520 && ( parentAssociation.leafKey != null ) )
521 {
522 Object value;
523 if ( parentAssociation.leafDisplayGroup != null )
524 {
525 value = parentGroup.valueForObject(
526 target, parentAssociation.leafKey );
527 }
528 else
529 {
530 value = parentAssociation.leafKey;
531 }
532
533
534 Object result = ValueConverter.getBoolean( value );
535 if ( result != null )
536 {
537 return ((Boolean)result).booleanValue();
538 }
539 }
540
541
542 return ( getChildCount() == 0 );
543 }
544
545 public Enumeration children()
546 {
547 int count = getChildCount();
548 Vector v = new Vector();
549 for ( int i = 0; i < count; i++ )
550 {
551 v.add( getChildNodeAt( i ) );
552 }
553 return v.elements();
554 }
555
556
557
558 public void insert(DisplayGroupNode aChild, int anIndex)
559 {
560 insertObjectAtIndex(
561 ((DisplayGroupNode)aChild).object(), anIndex );
562 }
563
564 public void remove(int index)
565 {
566 deleteObjectAtIndex( index );
567 }
568
569 /***
570 * Removes the node at the index corresponding
571 * to the index of the object.
572 */
573 public void remove(DisplayGroupNode node)
574 {
575 remove( getIndex( node ) );
576 }
577
578 /***
579 * Removes our object from the parent display group.
580 */
581 public void removeFromParent()
582 {
583 int index = parentGroup.displayedObjects().indexOfIdenticalObject( target );
584 if ( index != NSArray.NotFound )
585 {
586 parentGroup.deleteObjectAtIndex( index );
587 }
588 else
589 {
590 throw new WotonomyException(
591 "Object not found in parent group: " + target );
592 }
593 }
594
595 /***
596 * Removes our object from the parent display group
597 * and adds it to the end of the specified node's children.
598 */
599 public void setParent(DisplayGroupNode newParent)
600 {
601 removeFromParent();
602 newParent.insertObjectAtIndex(
603 object(), newParent.displayedObjects.size() );
604 }
605
606 /***
607 * Returns the value of the displayed property in the parent display group
608 * at the index that corresponds to the index of this node.
609 */
610 public Object getUserObject()
611 {
612 return valueForKey( parentAssociation.titlesKey );
613 }
614
615 /***
616 * Sets the value of the displayed property in the parent display group
617 * at the index that corresponds to the index of this node.
618 */
619 public void setUserObject( Object aValue )
620 {
621 setValueForKey( aValue, parentAssociation.titlesKey );
622 }
623
624 /***
625 * Returns a value from the object in the parent display group
626 * at the index that corresponds to the index of this node.
627 * For the root node, if the titles key is specified, the root
628 * label is returned, otherwise null is returned.
629 */
630 public Object valueForKey( String aKey )
631 {
632
633 if ( target == null )
634 {
635
636 if ( aKey == parentAssociation.titlesKey )
637 {
638 return parentAssociation.rootLabel();
639 }
640 return null;
641 }
642 return parentGroup.valueForObject( target, aKey );
643 }
644
645 /***
646 * Sets a value on the object in the parent display group
647 * at the index that corresponds to the index of this node.
648 * For the root node, this method only works if aKey is the
649 * titlesAspect's key, otherwise does nothing.
650 */
651 public void setValueForKey(Object aValue, String aKey)
652 {
653
654 if ( target == null )
655 {
656
657 if ( aKey == parentAssociation.titlesKey )
658 {
659 parentAssociation.setRootLabel( aValue );
660
661
662 fireNodesChanged ( treePath().getPath(),
663 new int[] { 0 },
664 new Object[] { this } );
665 }
666 return;
667 }
668
669 parentGroup.setValueForObject(
670 aValue, target, aKey );
671 }
672
673 /***
674 * Perform any clean up in this method.
675 * The node will not be reused after this method is called.
676 * This implementation removes itself from the parent's
677 * set of child nodes, sets target and datasource to null,
678 * and then calls disposeChildNodes().
679 */
680 protected void dispose()
681 {
682 if ( parentGroup != null )
683 {
684 ((DisplayGroupNode)parentGroup).childNodes.remove(
685 new ReferenceKey( target ) );
686 }
687 setTarget( (Object) null );
688 setDataSource( null );
689 disposeChildNodes();
690 }
691
692 /***
693 * Calls dispose() on all child nodes.
694 */
695 protected void disposeChildNodes()
696 {
697 Iterator i = new LinkedList(childNodes.values()).iterator();
698 while ( i.hasNext() )
699 {
700 ((DisplayGroupNode) i.next()).dispose();
701 }
702 }
703
704 /***
705 * Called after the target object posts a change notification.
706 * This implementation re-fetches which triggers
707 * updateDisplayedObjects to broadcast any tree events.
708 * This method marks the parent object as changed if:
709 * (1) this object is not registered in the editing context
710 * of the titles display group's data source (if any), AND
711 * (2) the children key is not in the list of attributes
712 * of the parent object's EOClassDescription.
713 */
714 public void targetChanged()
715 {
716
717 if ( target != null )
718 {
719
720
721
722
723 if ( isFetched() )
724 {
725 fetch();
726 }
727 else
728 {
729 updateDisplayedObjects();
730 }
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756 }
757 else
758 {
759 setObjectArray( parentAssociation.titlesDisplayGroup.displayedObjects() );
760 }
761
762
763
764 fireNodeChanged();
765 }
766
767 /***
768 * Fires a change event for this node.
769 */
770 public void fireNodeChanged()
771 {
772
773 if ( target != null )
774 {
775 int index = ((DisplayGroupNode)parentGroup).getIndex( this );
776 if ( ( index != -1 )
777 && ( treePath().getParentPath() != null ) )
778 {
779 fireNodesChanged (
780 treePath().getParentPath().getPath(),
781 new int[] { index },
782 new Object[] { this } );
783 }
784 }
785 }
786
787 Object[] previouslyDisplayedObjects = new Object[0];
788 /***
789 * Overridden to call to super, fire any tree events, and then
790 * call updateDisplayedObjects on all fetched child nodes.
791 * This method compares this node's displayed objects against
792 * the list of child nodes, synchronizes them, and then broadcasts
793 * only the necessary events to bring the view component up to date.
794 */
795 public void updateDisplayedObjects()
796 {
797
798
799
800 super.updateDisplayedObjects();
801
802
803 boolean proceed = true;
804 Object[] oldObjects = previouslyDisplayedObjects;
805 Object[] newObjects = displayedObjects.toArray();
806 if ( oldObjects.length == newObjects.length )
807 {
808 proceed = false;
809 for ( int i = 0; i < newObjects.length; i++ )
810 {
811 if ( oldObjects[i] != newObjects[i] )
812 {
813 proceed = true;
814 break;
815 }
816 }
817 }
818
819
820
821 previouslyDisplayedObjects = newObjects;
822
823 DisplayGroupNode node;
824 Iterator i = childNodes.values().iterator();
825 while ( i.hasNext() )
826 {
827 node = (DisplayGroupNode) i.next();
828 if ( !node.isFetchNeeded() )
829 {
830 node.updateDisplayedObjects();
831 }
832 }
833
834 if ( proceed )
835 {
836
837
838 fireEventsForChanges( oldObjects, newObjects );
839 }
840
841 }
842
843 /***
844 * Called by processRecentChanges to analyze the
845 * differences between the lists and broadcast the
846 * appropriate events.
847 */
848 protected void fireEventsForChanges(
849 Object[] oldObjects, Object[] newObjects )
850 {
851
852
853
854
855
856
857
858
859 int insertCount = 0;
860 int deleteCount = 0;
861 Object[] inserts = new Object[ newObjects.length ];
862 Object[] deletes = new Object[ oldObjects.length ];
863
864 int i;
865 int n = -1, o = -1;
866 int n1 = 0, o1 = 0;
867 int n2 = 0, o2 = 0;
868
869 while ( o1 < oldObjects.length && n1 < newObjects.length )
870 {
871 if ( newObjects[n1] == oldObjects[o1] )
872 {
873
874 o = o1;
875 n = n1;
876 }
877 else
878 {
879
880 o2 = o1;
881 n2 = n1;
882
883 while ( o2 < oldObjects.length || n2 < newObjects.length )
884 {
885 if ( o2 < oldObjects.length && newObjects[n1] == oldObjects[o2] )
886 {
887
888 for ( i = o1; i < o2; i++ )
889 {
890 deletes[i] = oldObjects[i];
891 deleteCount++;
892 }
893 o1 = o2;
894 o = o1;
895 n = n1;
896 break;
897 }
898 if ( n2 < newObjects.length && newObjects[n2] == oldObjects[o1] )
899 {
900
901 for ( i = n1; i < n2; i++ )
902 {
903 inserts[i] = newObjects[i];
904 insertCount++;
905 }
906 n1 = n2;
907 n = n1;
908 o = o1;
909 break;
910 }
911 o2++;
912 n2++;
913 }
914 }
915 if (n != n1)
916 {
917 inserts[n1] = newObjects[n1];
918 insertCount++;
919 deletes[o1] = oldObjects[o1];
920 deleteCount++;
921
922
923
924 n = n1;
925 o = o1;
926 }
927 o1++;
928 n1++;
929 }
930
931
932 for ( i = o+1; i < oldObjects.length; i++ )
933 {
934 deletes[i] = oldObjects[i];
935 deleteCount++;
936 }
937
938
939 for ( i = n+1; i < newObjects.length; i++ )
940 {
941 inserts[i] = newObjects[i];
942 insertCount++;
943 }
944
945
946
947
948
949
950
951
952 int c;
953 Object[] nodes;
954 int[] indices;
955
956
957 c = 0;
958 nodes = new Object[ deleteCount ];
959 indices = new int[ deleteCount ];
960 for ( i = 0; i < deletes.length; i++ )
961 {
962 if ( deletes[i] != null )
963 {
964 indices[c] = i;
965 nodes[c] = getChildNodeForObject( deletes[i] );
966 c++;
967 }
968 }
969 if ( c > 0 )
970 {
971
972 fireNodesRemoved( treePath().getPath(), indices, nodes );
973 }
974 deletes = nodes;
975
976
977 c = 0;
978 nodes = new Object[ insertCount ];
979 indices = new int[ insertCount ];
980 for ( i = 0; i < inserts.length; i++ )
981 {
982 if ( inserts[i] != null )
983 {
984 indices[c] = i;
985 nodes[c] = getChildNodeForObject( inserts[i] );
986 if ( nodes[c] == null )
987 {
988 nodes[c] = createChildNodeForObject( newObjects[i] );
989 }
990 c++;
991 }
992 }
993 if ( c > 0 )
994 {
995 fireNodesInserted( treePath().getPath(), indices, nodes );
996 }
997
998
999 int j;
1000 boolean found;
1001 for ( i = 0; i < deletes.length; i++ )
1002 {
1003 for ( j = 0; j < nodes.length; j++ )
1004 {
1005 if ( deletes[i] == nodes[j] ) break;
1006 }
1007
1008
1009 if ( j == nodes.length )
1010 {
1011 ((DisplayGroupNode)deletes[i]).dispose();
1012 }
1013 }
1014 }
1015
1016 /***
1017 * Sets the target object and creates an registers a target observer.
1018 * If target was not previously null, the existing observer is unregistered.
1019 * Protected access so subclasses and TreeModelAssociation can update our target.
1020 */
1021 public void setTarget( Object aTarget )
1022 {
1023 if ( target != null )
1024 {
1025 EOObserverCenter.removeObserver( targetObserver, target );
1026 targetObserver.discardPendingNotification();
1027 }
1028
1029 if ( aTarget != null )
1030 {
1031 target = aTarget;
1032 targetObserver = new TargetObserver( this );
1033 EOObserverCenter.addObserver( targetObserver, target );
1034 }
1035 }
1036
1037 /***
1038 * Returns the parent display group, or null if parent is root.
1039 */
1040 public DisplayGroupNode getParentGroup()
1041 {
1042 if ( parentGroup instanceof DisplayGroupNode )
1043 {
1044 return (DisplayGroupNode)parentGroup;
1045 }
1046
1047 return null;
1048 }
1049
1050 /***
1051 * Gets all descendants of the this node.
1052 */
1053 public List getDescendants()
1054 {
1055 return getDescendants( this, true );
1056 }
1057
1058 /***
1059 * Gets only the descendants of the this node
1060 * whose children has been loaded - no fetching
1061 * will occur. Useful for load-on-demand trees.
1062 */
1063 public List getLoadedDescendants()
1064 {
1065 return getDescendants( this, false );
1066 }
1067
1068
1069
1070 /***
1071 * Returns a list of all descendants of the
1072 * specified node. Unfetched nodes are traversed
1073 * only if forceLoad is true.
1074 * This implementation is a breadth-first traversal
1075 * of the nodes starting at the specified node.
1076 */
1077 static private List getDescendants( DisplayGroupNode aNode, boolean forceLoad )
1078 {
1079 if ( !forceLoad && !aNode.isFetched ) return NSArray.EmptyArray;
1080
1081 LinkedList result = new LinkedList();
1082 LinkedList queue = new LinkedList();
1083
1084 queue.add( aNode );
1085 while ( ! queue.isEmpty() )
1086 {
1087 checkNode( (DisplayGroupNode) queue.removeFirst(),
1088 queue, result, forceLoad );
1089 }
1090
1091 return result;
1092 }
1093
1094 /***
1095 * Adds each fetched child node of the specified node to
1096 * the result set (optionally forcing the child node to load)
1097 * and adding child node to the end of the queue.
1098 */
1099 static private void checkNode( DisplayGroupNode aNode,
1100 LinkedList aQueue, LinkedList aResult, boolean forceLoad )
1101 {
1102 DisplayGroupNode child;
1103 int count = aNode.getChildCount();
1104
1105 for ( int i = 0; i < count; i++ )
1106 {
1107 child = aNode.getChildNodeAt( i );
1108
1109
1110 if ( ( !child.isFetched ) && ( forceLoad ) )
1111 {
1112 child.fetch();
1113 }
1114 if ( child.isFetched )
1115 {
1116 aQueue.addLast( child );
1117 }
1118
1119 aResult.add( child );
1120 }
1121 }
1122
1123 /***
1124 * Overridden to not fetch on InvalidateAllObjectsInStoreNotification
1125 * unless we've already been fetched, preserving the load-on-demand
1126 * functionality.
1127 */
1128 public void objectsInvalidatedInEditingContext( NSNotification aNotification )
1129 {
1130 if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification
1131 .equals( aNotification.name() ) )
1132 {
1133
1134 if ( parentAssociation.isVisible( this ) && targetObserver != null )
1135 {
1136 targetObserver.objectWillChange( target );
1137 fireNodeChanged();
1138 }
1139 else
1140 setFetchNeeded( true );
1141 return;
1142 }
1143 else
1144 if ( ( EOEditingContext.ObjectsChangedInEditingContextNotification
1145 .equals( aNotification.name() ) )
1146 || ( EOEditingContext.EditingContextDidSaveChangesNotification
1147 .equals( aNotification.name() ) ) )
1148 {
1149 int index;
1150 Enumeration e;
1151 boolean didChange = false;
1152 NSDictionary userInfo = aNotification.userInfo();
1153
1154
1155 NSArray deletes = (NSArray) userInfo.objectForKey(
1156 EOObjectStore.DeletedKey );
1157 if ( deletes.indexOfIdenticalObject( target ) != NSArray.NotFound )
1158 {
1159
1160 if ( parentAssociation.isVisible( this ) && targetObserver != null )
1161 {
1162 targetObserver.objectWillChange( target );
1163 fireNodeChanged();
1164 }
1165 else
1166 setFetchNeeded( true );
1167 return;
1168 }
1169
1170
1171 NSArray invalidates = (NSArray) userInfo.objectForKey(
1172 EOObjectStore.InvalidatedKey );
1173 if ( invalidates != null &&
1174 invalidates.indexOfIdenticalObject( target ) != NSArray.NotFound )
1175 {
1176
1177 if ( parentAssociation.isVisible( this ) && targetObserver != null )
1178 {
1179 targetObserver.objectWillChange( target );
1180 fireNodeChanged();
1181 }
1182 else
1183 setFetchNeeded( true );
1184 return;
1185 }
1186
1187
1188 NSArray updates = (NSArray) userInfo.objectForKey(
1189 EOObjectStore.UpdatedKey );
1190 if ( updates.indexOfIdenticalObject( target ) != NSArray.NotFound )
1191 {
1192 if ( parentAssociation.isVisible( this ) && targetObserver != null )
1193 {
1194 targetObserver.objectWillChange( target );
1195 fireNodeChanged();
1196 if ( object() instanceof Component ) ((Component)object()).repaint();
1197 }
1198 else
1199 setFetchNeeded( true );
1200 return;
1201 }
1202 }
1203
1204 super.objectsInvalidatedInEditingContext( aNotification );
1205
1206 }
1207
1208
1209
1210 /***
1211 * Private class used to force a hashmap to
1212 * perform key comparisons by reference.
1213 */
1214 private class ReferenceKey
1215 {
1216 private int hashCode;
1217 private Object referent;
1218
1219 public ReferenceKey( Object anObject )
1220 {
1221 referent = anObject;
1222 hashCode = anObject.hashCode();
1223 }
1224
1225 /***
1226 * Returns the actual key's hash code.
1227 */
1228 public int hashCode()
1229 {
1230 return hashCode;
1231 }
1232
1233 /***
1234 * Compares by reference.
1235 */
1236 public boolean equals( Object anObject )
1237 {
1238 if ( anObject instanceof ReferenceKey )
1239 {
1240 return ((ReferenceKey)anObject).referent == referent;
1241 }
1242 return false;
1243 }
1244 }
1245
1246 /***
1247 * A private class to observe the target object of this node.
1248 */
1249 private class TargetObserver extends EODelayedObserver
1250 {
1251 Reference ref;
1252
1253 /***
1254 * Pass in the display group node that will be updated
1255 * when the target changes.
1256 */
1257 public TargetObserver( DisplayGroupNode aDisplayGroup )
1258 {
1259 ref = new WeakReference( aDisplayGroup );
1260 }
1261
1262 /***
1263 * Repopulate our display group, and calculate the deltas
1264 * so we can broadcast appropriate events.
1265 */
1266 public void subjectChanged ()
1267 {
1268 DisplayGroupNode node = (DisplayGroupNode) ref.get();
1269 if ( node == null ) return;
1270
1271
1272 node.targetChanged();
1273 }
1274 }
1275
1276 }
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518