View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Michael Powers
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.web;
20  
21  import java.util.Collection;
22  import java.util.Enumeration;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Observable;
28  
29  import net.wotonomy.control.EODataSource;
30  import net.wotonomy.control.EOEditingContext;
31  import net.wotonomy.control.EOKeyValueCoding;
32  import net.wotonomy.control.EOKeyValueCodingSupport;
33  import net.wotonomy.control.EOObjectStore;
34  import net.wotonomy.control.EOObserverCenter;
35  import net.wotonomy.control.EOObserving;
36  import net.wotonomy.control.EOQualifier;
37  import net.wotonomy.control.EOSortOrdering;
38  import net.wotonomy.control.OrderedDataSource;
39  import net.wotonomy.control.PropertyDataSource;
40  import net.wotonomy.foundation.NSArray;
41  import net.wotonomy.foundation.NSDictionary;
42  import net.wotonomy.foundation.NSMutableArray;
43  import net.wotonomy.foundation.NSMutableDictionary;
44  import net.wotonomy.foundation.NSNotification;
45  import net.wotonomy.foundation.NSNotificationCenter;
46  import net.wotonomy.foundation.NSRange;
47  import net.wotonomy.foundation.NSSelector;
48  import net.wotonomy.foundation.internal.Duplicator;
49  import net.wotonomy.foundation.internal.WotonomyException;
50  
51  /***
52  * WODisplayGroup manages a set of objects,
53  * allowing them to be sorted, batched, and filtered.
54  * WODisplay also acts as a bridge to the wotonomy's
55  * control package, including WODisplayGroup and 
56  * EOEditingContext.
57  *
58  * @author michael@mpowers.net
59  * @author $Author: cgruber $
60  * @version $Revision: 905 $
61  */
62  public class WODisplayGroup extends Observable
63                           implements EOObserving, EOEditingContext.Editor, 
64                           java.io.Serializable
65  {
66      /***
67      * Notification sent when the display group is about to fetch.
68      */ 
69      public static final String DisplayGroupWillFetchNotification
70          = "DisplayGroupWillFetchNotification";
71      
72      private static boolean
73          globalDefaultForValidatesChangesImmediately = true;
74      private static String
75          globalDefaultStringMatchFormat = "caseInsensitiveLike";
76      private static String
77          globalDefaultStringMatchOperator = "%@*";
78  
79      protected NSMutableArray allObjects;
80      protected NSArray allObjectsProxy;
81      protected NSMutableArray displayedObjects;
82      protected NSArray displayedObjectsProxy;
83      protected NSMutableArray selectedObjects;
84      protected NSArray selectedObjectsProxy;
85      protected NSMutableArray selectedIndexes;
86  
87      private String defaultStringMatchOperator;
88      private String defaultStringMatchFormat;
89  
90      private boolean validatesChangesImmediately;
91      private Object delegate;
92      private EODataSource dataSource;
93      private EOQualifier qualifier;
94      private NSMutableArray sortOrderings;
95      private NSArray sortOrderingsProxy;
96  
97      private NSArray localKeys;
98      private NSDictionary insertedObjectDefaultValues;
99      private boolean fetchesOnLoad;
100     private boolean selectsFirstObjectAfterFetch;
101     private boolean usesOptimisticRefresh;
102     private boolean inQueryMode;
103 
104     // change detection: package access for helper classes
105     boolean selectionChanged;
106     int updatedObjectIndex;
107     
108     // batching
109     private int batchIndex;
110     private int batchSize;
111 
112     // this property is not in the spec
113     private boolean compareByReference = false;
114 
115     private EOObserving lastGroupObserver;
116     
117     /***
118     * Creates a new display group.
119     */
120     public WODisplayGroup()
121     {
122         validatesChangesImmediately =
123             globalDefaultForValidatesChangesImmediately();
124         defaultStringMatchOperator =
125             globalDefaultStringMatchFormat();
126         defaultStringMatchFormat =
127             globalDefaultStringMatchOperator();
128 
129         allObjects = new ObservableArray( this );
130         allObjectsProxy = NSArray.arrayBackedByList( allObjects );
131         displayedObjects = new NSMutableArray();
132         displayedObjectsProxy = NSArray.arrayBackedByList( displayedObjects );
133         selectedObjects = new NSMutableArray();
134         selectedObjectsProxy = NSArray.arrayBackedByList( selectedObjects );
135         sortOrderings = new NSMutableArray();
136         sortOrderingsProxy = NSArray.arrayBackedByList( sortOrderings );
137         selectedIndexes = new NSMutableArray();
138 
139         delegate = null;
140         dataSource = null;
141         qualifier = null;
142 
143         localKeys = new NSArray(); // not implemented
144         insertedObjectDefaultValues = new NSDictionary();
145         fetchesOnLoad = false; // not implemented
146         selectsFirstObjectAfterFetch = false;
147         usesOptimisticRefresh = false;
148         inQueryMode = false; // not implemented
149 
150         selectionChanged = false;
151         updatedObjectIndex = -1;
152         
153         batchIndex = 0;
154         batchSize = 0;
155     }
156 
157 
158 
159     // specify optional data source
160 
161     /***
162     * Sets the data source that will be used by
163     * this display group.
164     */
165     public void setDataSource ( EODataSource aDataSource )
166     {
167         if ( ( dataSource != null )
168         &&   ( dataSource.editingContext() != null ) )
169         {
170             // un-register for notifications from existing parent store
171             NSNotificationCenter.defaultCenter().removeObserver(
172                 this, null, dataSource.editingContext() );
173             dataSource.editingContext().removeEditor( this );
174             if ( dataSource.editingContext().messageHandler() == this )
175             {
176                 dataSource.editingContext().setMessageHandler( null );
177             }
178             
179         }
180 
181         dataSource = aDataSource;
182 
183         if ( ( dataSource != null )
184         &&   ( dataSource.editingContext() != null ) )
185         {
186             // register for notifications from parent store
187             NSNotificationCenter.defaultCenter().addObserver(
188                 this, new NSSelector( "objectsInvalidatedInEditingContext",
189                     new Class[] { NSNotification.class } ),
190                 null, dataSource.editingContext() );
191             
192             // add ourselves as editor
193             dataSource.editingContext().addEditor( this );
194             
195             // add ourselves as message handler if no such handler exists
196             if ( dataSource.editingContext().messageHandler() == null )
197             {
198                 dataSource.editingContext().setMessageHandler( this );
199             }
200         }
201     }
202     
203     /***
204     * Returns the current data source backing this display group,
205     * or null if no dataSource is currently used.
206     */
207     public EODataSource dataSource()
208     {
209         return dataSource;
210     }
211 
212     /***
213     * Returns the key by which this display group is bound a master 
214     * display group, or null if this is not a detail display group.
215     */
216     public String detailKey()
217     {
218         if ( dataSource instanceof PropertyDataSource )
219         {
220             return ((PropertyDataSource)dataSource).key();
221         }
222         return null;
223     }
224     
225     /***
226     * Sets the key by which this display group is bound a master 
227     * display group.  Does nothing if this is not a detail display group.
228     */
229     public void setDetailKey( String aKey )
230     {
231         if ( dataSource instanceof PropertyDataSource )
232         {
233             ((PropertyDataSource)dataSource).setKey( aKey );
234         }
235     }
236     
237     /***
238     * Returns whether the data source is a detail data source,
239     * suggesting that this is a detail display group.
240     */
241     public boolean hasDetailDataSource()
242     {
243         return ( dataSource instanceof PropertyDataSource );
244     }
245     
246     /***
247     * Returns the selected object in the master display group,
248     * or null if this is not a detail display group.
249     */
250     public Object masterObject()
251     {
252         if ( dataSource instanceof PropertyDataSource )
253         {
254             return ((PropertyDataSource)dataSource).source();
255         }
256         return null;
257     }
258 
259     /***
260     * Sets the master object in the detail data source.
261     * Does nothing if there is no detail data source.
262     */
263     public void setMasterObject( Object anObject )
264     {
265         if ( dataSource instanceof PropertyDataSource )
266         {
267             ((PropertyDataSource)dataSource).setSource( anObject );
268         }
269     }
270 
271     // specify optional delegate
272 
273     /***
274     * Sets the display group delegate that
275     * will be used by this display group.
276     */
277     public void setDelegate ( Object aDelegate )
278     {
279         delegate = aDelegate;
280     }
281 
282    /***
283     * Returns the current delegate for this display group,
284     * or null if no delegate is currently set.
285     */
286     public Object delegate()
287     {
288         return delegate;
289     }
290 
291 
292 
293     // display group configuration
294 
295     /***
296     * Returns the current string matching format.
297     * If not set, defaults to "%@*".
298     */
299     public String defaultStringMatchFormat()
300     {
301         return defaultStringMatchFormat;
302     }
303 
304     /***
305     * Returns the current string matching operator.
306     * If not set, defaults to "caseInsensitiveLike".
307     */
308     public String defaultStringMatchOperator()
309     {
310         return defaultStringMatchOperator;
311     }
312 
313     /***
314     * Sets the display group and associations to edit a
315     * "query by example" query object.  This method is
316     * used for target/action connections.
317     */
318     public void enterQueryMode ( Object aSender )
319     {
320         throw new RuntimeException( "Not implemented yet." );
321     }
322 
323     /***
324     * Returns whether this display group should immediate
325     * fetch when loaded.
326     */
327     public boolean fetchesOnLoad()
328     {
329         return fetchesOnLoad;
330     }
331 
332     /***
333     * Returns whether this display group is in "query by
334     * example" mode.
335     */
336     public boolean inQueryMode()
337     {
338         return inQueryMode;
339     }
340 
341     /***
342     * Returns a Map of default values that are applied
343     * to new objects that are inserted into the list.
344     */
345     public NSDictionary insertedObjectDefaultValues()
346     {
347         return insertedObjectDefaultValues;
348     }
349 
350     /***
351     * Returns the keys that were declared when read from
352     * an external resource file.
353     */
354     public NSArray localKeys()
355     {
356         return localKeys;
357     }
358 
359     /***
360     * Sets whether this display group will select the
361     * first object in the list after a fetch.
362     */
363     public boolean selectsFirstObjectAfterFetch()
364     {
365         return selectsFirstObjectAfterFetch;
366     }
367 
368     /***
369     * Sets the default string matching format that
370     * will be used by this display group.
371     */
372     public void setDefaultStringMatchFormat ( String aFormat )
373     {
374         throw new RuntimeException( "Not implemented yet." );
375     }
376 
377     /***
378     * Sets the default string matching operator that
379     * will be used by this display group.
380     */
381     public void setDefaultStringMatchOperator ( String anOperator )
382     {
383         throw new RuntimeException( "Not implemented yet." );
384     }
385 
386     /***
387     * Sets whether this display group will fetch objects
388     * from its data source on load.
389     */
390     public void setFetchesOnLoad ( boolean willFetch )
391     {
392         fetchesOnLoad = willFetch;
393     }
394 
395     /***
396     * Sets whether this display group is in "query by example"
397     * mode.  If true, all associations will bind to a special
398     * "example" object.
399     */
400     public void setInQueryMode ( boolean isInQueryMode )
401     {
402         inQueryMode = isInQueryMode;
403     }
404 
405     /***
406     * Sets the mapping that contains the values that will
407     * be applied to new objects inserted into the display group.
408     */
409     public void setInsertedObjectDefaultValues ( Map aMap )
410     {
411         insertedObjectDefaultValues = new NSDictionary( aMap );
412     }
413 
414     /***
415     * Sets the keys that are declared when instantiated from
416     * an external resource file.
417     */
418     public void setLocalKeys ( List aKeyList )
419     {
420         localKeys = new NSArray( (Collection) aKeyList );
421     }
422 
423     /***
424     * Sets whether the first object in the list will be
425     * selected after a fetch.
426     */
427     public void setSelectsFirstObjectAfterFetch (
428         boolean selectsFirst )
429     {
430         selectsFirstObjectAfterFetch = selectsFirst;
431     }
432 
433     /***
434     * Sets the order of the keys by which this display group
435     * will be ordered after a fetch or after a call to
436     * updateDisplayedObjects().  The elements in the display
437     * group will be sorted first by the first key, within
438     * the first key, by the second key, and so on.
439     */
440     public void setSortOrderings ( List aList )
441     {
442         sortOrderings.removeAllObjects();
443 
444         Object o;
445         Iterator it = aList.iterator();
446         while ( it.hasNext() )
447         {
448             o = it.next();
449             // handle the convenience of specifying just a key
450             if ( ! ( o instanceof EOSortOrdering ) )
451             {
452                 o = new EOSortOrdering(
453                     o.toString(), EOSortOrdering.CompareAscending );
454             }
455             sortOrderings.add( o );
456         }
457     }
458 
459     /***
460     * Sets whether only changed objects are refreshed (optimistic),
461     * or whether all objects are refreshed (pessimistic, default).
462     * By default, when the display group receives notification that
463     * one of its objects has changed, updateDisplayedObjects is called.
464     */
465     public void setUsesOptimisticRefresh ( boolean isOptimistic )
466     {
467         usesOptimisticRefresh = isOptimistic;
468     }
469 
470     /***
471     * Sets whether changes made by associations are validated
472     * immediately, or when changes are saved.
473     */
474     public void setValidatesChangesImmediately (
475         boolean validatesImmediately )
476     {
477         validatesChangesImmediately = validatesImmediately;
478     }
479 
480     /***
481     * Returns a read-only List of sort orderings for this display group.
482     */
483     public NSArray sortOrderings()
484     {
485         return sortOrderingsProxy;
486     }
487 
488     /***
489     * Returns whether this display group refreshes only
490     * the changed objects or all objects on refresh.
491     */
492     public boolean usesOptimisticRefresh()
493     {
494         return usesOptimisticRefresh;
495     }
496 
497     /***
498     * Returns whether this display group validates changes
499     * immediately.  Otherwise, validation should occur when
500     * changes are saved.  Default is the global default,
501     * which is initially true.
502     */
503     public boolean validatesChangesImmediately()
504     {
505         return validatesChangesImmediately;
506     }
507 
508 
509     // qualification
510 
511     /***
512     * Returns a qualifier that will be applied all the objects
513     * in this display group to determine which objects will
514     * be displayed.
515     */
516     public EOQualifier qualifier()
517     {
518         return qualifier;
519     }
520 
521     /***
522     * Returns a new qualifier built from the three query
523     * value maps: greater than, equal to, and less than.
524     */
525     public EOQualifier qualifierFromQueryValues()
526     {
527         //TODO: assemble qualifier from query values
528 
529         return new EOQualifier()
530         {
531             // use inner class until we actually implement one
532             public EOQualifier qualifierWithBindings(
533                 Map aMap,
534                 boolean requireAll )
535             {
536                 return null;
537             }
538             public Throwable
539             validateKeysWithRootClassDescription( Class aClass )
540             {
541                 return null;
542             }
543                         public boolean evaluateWithObject(Object o)
544                         {
545                           return false;
546                         }
547         };
548     }
549 
550     /***
551     * Calls qualifierFromQueryValues(), applies the result
552     * to the data source, and calls fetch().
553     */
554     public void qualifyDataSource()
555     {
556         throw new RuntimeException( "Not implemented yet." );
557     }
558 
559     /***
560     * Calls qualifierFromQueryValues(), sets the qualifier
561     * with setQualifier(), and calls updateDisplayedObjects().
562     */
563     public void qualifyDisplayGroup()
564     {
565         setQualifier( qualifierFromQueryValues() );
566         updateDisplayedObjects();
567     }
568 
569     /***
570     * Returns a Map containing the mappings of keys
571     * to binding query values.
572     */
573     public NSDictionary queryBindingValues()
574     {
575         throw new RuntimeException( "Not implemented yet." );
576     }
577 
578     /***
579     * Returns a Map containing the mappings of keys
580     * to binding query values.
581     */
582     public NSMutableDictionary queryBindings()
583     {
584         throw new RuntimeException( "Not implemented yet." );
585     }
586 
587     /***
588     * Returns a Map containing the mappings of keys
589     * to binding query values for a matching query.
590     */
591     public NSMutableDictionary queryMatch()
592     {
593         throw new RuntimeException( "Not implemented yet." );
594     }
595 
596     /***
597     * Returns a Map containing the mappings of keys
598     * to binding query values for a minimum value query.
599     */
600     public NSMutableDictionary queryMin()
601     {
602         throw new RuntimeException( "Not implemented yet." );
603     }
604 
605     /***
606     * Returns a Map containing the mappings of keys
607     * to binding query values for a maximum value query.
608     */
609     public NSMutableDictionary queryMax()
610     {
611         throw new RuntimeException( "Not implemented yet." );
612     }
613 
614     /***
615     * Returns a Map containing the mappings of keys
616     * to operator values.
617     */
618     public NSMutableDictionary queryOperator()
619     {
620         throw new RuntimeException( "Not implemented yet." );
621     }
622 
623     /***
624     * Returns a list containing all supported qualifier operators.
625     */
626     public NSArray allQualifierOperators()
627     {
628         throw new RuntimeException( "Not implemented yet." );
629     }
630 
631     /***
632     * Returns a Map containing the mappings of keys
633     * to operator values.
634     */
635     public NSDictionary queryOperatorValues()
636     {
637         throw new RuntimeException( "Not implemented yet." );
638     }
639 
640     /***
641     * Sets the qualifier that will be used by
642     * updateDisplayedObjects() to filter displayed objects.
643     */
644     public void setQualifier ( EOQualifier aQualifier )
645     {
646         qualifier = aQualifier;
647     }
648 
649     /***
650     * Sets the mapping that contains the mappings of keys
651     * to binding values.
652     */
653     public void setQueryBindingValues ( Map aMap )
654     {
655         throw new RuntimeException( "Not implemented yet." );
656     }
657 
658     /***
659     * Sets the mapping that contains the mappings of keys
660     * to operator values.
661     */
662     public void setQueryOperatorValues ( Map aMap )
663     {
664         throw new RuntimeException( "Not implemented yet." );
665     }
666 
667 
668 
669     // qualifier query values
670 
671     /***
672     * Returns a Map containing the mappings of keys
673     * to query values that will be used to test for equality.
674     */
675     public NSDictionary equalToQueryValues()
676     {
677         throw new RuntimeException( "Not implemented yet." );
678     }
679 
680     /***
681     * Returns a Map containing the mappings of keys
682     * to query values that will be used to test for greater value.
683     */
684     public NSDictionary greaterThanQueryValues()
685     {
686         throw new RuntimeException( "Not implemented yet." );
687     }
688 
689     /***
690     * Returns a Map containing the mappings of keys
691     * to query values that will be used to test for lesser value.
692     */
693     public NSDictionary lessThanQueryValues()
694     {
695         throw new RuntimeException( "Not implemented yet." );
696     }
697 
698     /***
699     * Sets the Map that contains the mappings of keys
700     * to query values that will be used to test for equality.
701     */
702     public void setEqualToQueryValues ( Map aMap )
703     {
704         throw new RuntimeException( "Not implemented yet." );
705     }
706 
707     /***
708     * Sets the mapping that contains the mappings of keys
709     * to query values that will be used to test for greater value.
710     */
711     public void setGreaterThanQueryValues ( Map aMap )
712     {
713         throw new RuntimeException( "Not implemented yet." );
714     }
715 
716     /***
717     * Sets the mapping that contains the mappings of keys
718     * to query values that will be used to test for lesser value.
719     */
720     public void setLessThanQueryValues ( Map aMap )
721     {
722         throw new RuntimeException( "Not implemented yet." );
723     }
724 
725     /***
726     * Deprecated: returns true.
727     */
728     public boolean endEditing()
729     {
730         return true;
731     }
732 
733 
734     // object management
735 
736     /***
737     * Returns a read-only List containing all objects managed by the display group.
738     * This includes those objects not visible due to disqualification.
739     */
740     public NSArray allObjects()
741     { //System.out.println( "avoided allocation: allObjects" );
742         return allObjectsProxy;
743     }
744     
745     /***
746     * Returns the total number of batches held by this display group.
747     */
748     public int batchCount()
749     {
750         if ( batchSize < 1 ) return 1;
751         int count = displayedObjects.count();
752         if ( count == 0 ) return 1;
753         return ((count-1) / batchSize) + 1;
754     }
755     
756     /***
757     * Returns the index of the currently displayed batch.
758     */
759     public int currentBatchIndex()
760     {
761         return batchIndex;
762     }
763     
764     /***
765     * Sets the index of the currently displayed batch.
766     */
767     public void setCurrentBatchIndex( int aBatchIndex )
768     {
769         batchIndex = aBatchIndex;
770         updateDisplayedObjects();
771     }
772     
773     /***
774     * Sets the displayed objects to the batch containing
775     * the first selected object, and updates the current
776     * batch index, and returns null to force a page reload.
777     * Displays the first batch is there is no selection.
778     */
779     public Object displayBatchContainingSelectedObject()
780     {
781         if ( batchSize < 1 ) return null;
782         NSArray indexes = selectionIndexes();
783         if ( indexes.count() > 0 ) 
784         {
785             batchIndex = 
786                 ((Number)indexes.objectAtIndex( 0 )).intValue() / batchSize;
787         }
788         else
789         {
790             batchIndex = 0;
791         }
792         updateDisplayedObjects();
793         return null;
794     }
795     
796     /***
797     * Sets the displayed objects to the next batch
798     * and updates the current batch index, and returns null
799     * to force a page reload.  If there is no next batch
800     * the first batch is displayed.
801     */
802     public Object displayNextBatch()
803     {
804         batchIndex = (batchIndex + 1) % batchCount();
805         updateDisplayedObjects();
806         return null;
807     }
808     
809     /***
810     * Sets the displayed objects to the next batch
811     * and updates the current batch index, and returns null
812     * to force a page reload.  If there is no previous
813     * batch, the last batch is displayed.
814     */
815     public Object displayPreviousBatch()
816     {
817         batchIndex--;
818         if ( batchIndex < 0 ) batchIndex = batchCount() - 1;
819         updateDisplayedObjects();
820         return null;
821     }
822     
823     /***
824     * Returns whether the displayed objects have been batched.
825     */
826     public boolean hasMultipleBatches()
827     {
828         return batchCount() > 1;
829     }
830     
831     /***
832     * Returns the one-based index within the displayed objects list
833     * of the first displayed object in the current batch.
834     */
835     public int indexOfFirstDisplayedObject()
836     {
837         if ( batchSize < 1 ) return 1;
838         return batchIndex * batchSize + 1;
839     }
840     
841     /***
842     * Returns the one-based index within the displayed objects list
843     * of the first last object in the current batch.
844     */
845     public int indexOfLastDisplayedObject()
846     {
847         if ( batchSize < 1 ) return displayedObjects.count();
848         return Math.min( 
849             ((batchIndex+1) * batchSize),
850             displayedObjects.count() );
851     }
852     
853     /***
854     * Returns the number of objects per batch.
855     */
856     public int numberOfObjectsPerBatch()
857     {
858         return batchSize;
859     }
860     
861     /***
862     * Returns the number of objects per batch.
863     */
864     public void setNumberOfObjectsPerBatch( int aSize )
865     {
866         batchSize = aSize;
867         updateDisplayedObjects();
868     }
869     
870     /***
871     * Clears the current selection.
872     * @return True is the selection was cleared,
873     * False if the selection could not be cleared
874     * @see #setSelectionIndexes
875     */
876     public boolean clearSelection()
877     {
878         Object result = notifyDelegate(
879             "displayGroupShouldChangeSelection",
880             new Class[] { WODisplayGroup.class, List.class },
881             new Object[] { this, new NSArray( selectedObjects ) } );
882         if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
883         {
884             return false;
885         }
886 
887         selectionChanged = true;
888         willChange();
889 
890         selectedObjects.removeAllObjects();
891         selectedIndexes.removeAllObjects();
892 
893         notifyDelegate(
894             "displayGroupDidChangeSelection",
895             new Class[] { WODisplayGroup.class },
896             new Object[] { this } );
897         notifyDelegate(
898             "displayGroupDidChangeSelectedObjects",
899             new Class[] { WODisplayGroup.class },
900             new Object[] { this } );
901 
902         return true;
903     }
904     
905     /***
906     * Convenience for binding to a component action:
907     * calls deleteSelection() and then displayBatchContainingSelectedObject()
908     * and returns null, which is a suitable result from a component action.
909     */
910     public Object delete()
911     {
912         deleteSelection();
913         displayBatchContainingSelectedObject();
914         return null;
915     }
916 
917     /***
918     * Deletes the object at the specified index,
919     * notifying the delegate before and after the operation,
920     * and then updating the selection if needed.
921     * @return True if delete was successful, false if the
922     * object was not deleted.
923     */
924     public boolean deleteObjectAtIndex ( int anIndex )
925     {
926         Object target = displayedObjects.objectAtIndex( anIndex );
927 
928         Object result = notifyDelegate(
929             "displayGroupShouldDeleteObject",
930             new Class[] { WODisplayGroup.class, Object.class },
931             new Object[] { this, target } );
932         if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
933         {
934             return false;
935         }
936 
937         deleteObjectAtIndexNoNotify( anIndex );
938         
939         if ( dataSource != null )
940         {
941             dataSource.deleteObject( target );
942         }
943 
944         notifyDelegate(
945             "displayGroupDidDeleteObject",
946             new Class[] { WODisplayGroup.class, Object.class },
947             new Object[] { this, target } );
948 
949         return true;
950     }
951 
952     private void deleteObjectAtIndexNoNotify ( int anIndex )
953     {
954         Object target = displayedObjects.objectAtIndex( anIndex );
955 
956         int i;
957 
958         // remove from selected objects if necessary
959         i = indexOf( selectedObjects, target );
960         if ( i != NSArray.NotFound )
961         {
962             selectionChanged = true;
963             willChange(); // notify before removing
964             selectedObjects.removeObjectAtIndex( i );
965             selectedIndexes.remove( new Integer( i ) ); // comps by value
966         }
967         else // notify - no selection change needed
968         {
969             willChange();
970         }
971 
972         // remove from all objects
973         i = indexOf( allObjects, target );
974         if ( i != NSArray.NotFound )
975         {
976             allObjects.removeObjectAtIndex( i );
977         }
978         else // otherwise should never happen
979         {
980 //          throw new WotonomyException(
981 //              "Displayed object not found in allObjects" );
982         }
983 
984         // remove from displayed objects
985         displayedObjects.removeObjectAtIndex( anIndex );
986     }
987 
988     /***
989     * Deletes the currently selected objects.
990     * This implementation calls deleteObjectAtIndex() for
991     * each index in the selection list, immediately returning
992     * false if any delete operation fails.
993     * @return True if all selected objects were deleted,
994     * false if any deletion failed.
995     */
996     public boolean deleteSelection()
997     {
998         int i;
999         boolean result = true;
1000 
1001         Enumeration e = new NSArray( selectedObjects ).objectEnumerator();
1002         while ( e.hasMoreElements() )
1003         {
1004             i = indexOf( displayedObjects, e.nextElement() );
1005             if ( i == NSArray.NotFound )
1006             {
1007                 // should never happen
1008                 throw new WotonomyException(
1009                     "Selected object not found in displayedObjects" );
1010             }
1011             result = result && deleteObjectAtIndex( i );
1012         }
1013 
1014         return result;
1015     }
1016 
1017     /***
1018     * Returns a read-only List of all objects in the display group
1019     * that are currently displayed by the associations.
1020     */
1021     public NSArray displayedObjects()
1022     { // System.out.println( "avoided allocation: displayedObjects" );
1023         if ( batchSize < 1 ) return displayedObjectsProxy;
1024         return displayedObjectsProxy.subarrayWithRange(
1025             new NSRange( batchIndex * batchSize, batchSize ) );
1026     }
1027 
1028     /***
1029     * Requests a list of objects from the DataSource
1030     * and calls setObjectArray to populate the list.
1031     * More specifically, calls endEditing(), asks the
1032     * delegate, fetches the objects, notifies the delegate,
1033     * and populates the list.  Returns null to force a
1034     * page reload.
1035     */
1036     public Object fetch()
1037     {
1038         endEditing();
1039 
1040         if ( dataSource == null )
1041         {
1042             return null;
1043         }
1044 
1045         Object result = notifyDelegate(
1046             "displayGroupShouldFetch",
1047             new Class[] { WODisplayGroup.class },
1048             new Object[] { this } );
1049         if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
1050         {
1051             return null;
1052         }
1053 
1054         NSNotificationCenter.defaultCenter().postNotification(
1055             DisplayGroupWillFetchNotification, this, new NSDictionary() );
1056         
1057         NSArray objectList = dataSource.fetchObjects();
1058 
1059         notifyDelegate(
1060             "displayGroupDidFetchObjects",
1061             new Class[] { WODisplayGroup.class, List.class },
1062             new Object[] { this, objectList } );
1063 
1064         setObjectArray( objectList );
1065 
1066         if ( ( selectsFirstObjectAfterFetch ) && ( displayedObjects.size() > 0 ) )
1067         {
1068             setSelectionIndexes( new NSArray( new Integer( 0 ) ) );
1069         }
1070 
1071         return null;
1072    }
1073    
1074    /***
1075    * Convenience to call insertNewObjectAtIndex with the current selection plus one,
1076    * or at the end of the list if there is no selection.  
1077    * Returns null to force a page reload.
1078    */
1079    public Object insert()
1080    {
1081        NSArray indexes = selectionIndexes();
1082        int size = indexes.count();
1083        if ( size == 0 ) 
1084        {
1085            insertNewObjectAtIndex( displayedObjects.count() );
1086        }
1087        else
1088        {
1089            insertNewObjectAtIndex( 
1090                ((Number)selectedIndexes.objectAtIndex( size-1 )).intValue()+1 );
1091        }
1092        return null;
1093    }
1094 
1095     /***
1096     * Creates a new object at the specified index.
1097     * Calls insertObjectAtIndex() with the result
1098     * from sending createObject() to the data source.
1099     * @return the newly created object.
1100     */
1101     public Object insertNewObjectAtIndex ( int anIndex )
1102     {
1103         Object result = null;
1104         if ( dataSource != null )
1105         {
1106             result = dataSource.createObject();
1107         }
1108         if ( result != null )
1109         {
1110             if ( insertedObjectDefaultValues != null )
1111             {
1112                 Duplicator.writePropertiesForObject(
1113                     insertedObjectDefaultValues, result );
1114             }
1115             insertObjectAtIndex( result, anIndex );
1116         }
1117         else // create failed
1118         {
1119             if ( delegate() != null )
1120             {
1121                 NSSelector selector = new NSSelector(
1122                     "displayGroupCreateObjectFailed",
1123                     new Class[] { WODisplayGroup.class, EODataSource.class } );
1124                 if ( selector.implementedByObject( delegate() ) )
1125                 {
1126                     try
1127                     {
1128                         selector.invoke( delegate(), new Object[] { this, dataSource } );
1129                         return result;
1130                     }
1131                     catch ( Exception exc )
1132                     {
1133                         System.err.println( "Error notifying delegate: displayGroupCreateObjectFailed" );
1134                         exc.printStackTrace();
1135                     }
1136                 }                    
1137             }
1138         }
1139         return result;
1140     }
1141 
1142     /***
1143     * Inserts the specified object into the list at
1144     * the specified index.
1145     */
1146     public void insertObjectAtIndex ( Object anObject, int anIndex )
1147     {
1148         Object result = notifyDelegate(
1149             "displayGroupShouldInsertObject",
1150             new Class[] { WODisplayGroup.class, Object.class, int.class },
1151             new Object[] { this, anObject, new Integer(anIndex) } );
1152         if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
1153         {
1154             return;
1155         }
1156 
1157         updatedObjectIndex = anIndex;
1158         willChange();
1159 
1160         int i;
1161 
1162         // add to all objects
1163         if ( anIndex == displayedObjects.size() )
1164         {
1165             allObjects.addObject( anObject );
1166         }
1167         else // insert before same object
1168         {
1169             Object target = displayedObjects.objectAtIndex( anIndex );
1170             int targetIndex = indexOf( allObjects, target );
1171             if ( targetIndex != NSArray.NotFound )
1172             {
1173                 allObjects.insertObjectAtIndex( anObject, targetIndex );
1174             }
1175             else // should never happen
1176             {
1177                 throw new WotonomyException(
1178                     "Could not find displayed object in all objects list: "
1179                     + target );
1180             }
1181         }
1182 
1183         // add to displayed objects
1184         displayedObjects.insertObjectAtIndex( anObject, anIndex );
1185 
1186         if ( dataSource != null )
1187         {
1188             if ( dataSource instanceof OrderedDataSource )
1189             {
1190                 ((OrderedDataSource)dataSource).insertObjectAtIndex(
1191                     anObject, anIndex );
1192             }
1193             else
1194             {
1195                 dataSource.insertObject( anObject );
1196             }
1197         }
1198 
1199         notifyDelegate(
1200             "displayGroupDidInsertObject",
1201             new Class[] { WODisplayGroup.class, Object.class },
1202             new Object[] { this, anObject } );
1203     }
1204 
1205     /***
1206     * Sets contentsChanged to true and notifies all observers.
1207     */
1208     public void redisplay()
1209     {
1210         willChange();
1211     }
1212 
1213     /***
1214     * Sets the selection to the next displayed object after the current
1215     * selection.  If the last object is selected, or if no object
1216     * is selected, then the first object becomes selected.
1217     * If multiple items are selected, the first selected item is
1218     * considered the selected item for the purposes of this method.
1219     * Does not call redisplay().
1220     * @return null to force a page reload.
1221     */
1222     public Object selectNext()
1223     {
1224         int count = displayedObjects.count();
1225         if ( count == 0 ) return null;
1226         if ( count == 1 )
1227         {
1228             selectObject( displayedObjects.objectAtIndex( 0 ) );
1229             return null;
1230         }
1231 
1232         int i = -1;
1233         Object selectedObject = selectedObject();
1234         if ( selectedObject != null )
1235         {
1236             i = indexOf( displayedObjects, selectedObject );
1237         }
1238         if ( i == NSArray.NotFound ) i = -1;
1239 
1240         // select next object
1241         i++;
1242         if ( i != displayedObjects.count() )
1243         {
1244             // set to next object
1245             selectedObject = displayedObjects.objectAtIndex( i );
1246         }
1247         else // out of range
1248         {
1249             // set to null
1250             selectedObject = displayedObjects.objectAtIndex( 0 );
1251         }
1252 
1253         selectObject( selectedObject );
1254         return null;
1255     }
1256 
1257     /***
1258     * Sets the selection to the specified object.
1259     * If the specified object is null or does not exist
1260     * in the list of displayed objects, the selection
1261     * will be cleared.
1262     * @return true if the object was selected.
1263     */
1264     public boolean selectObject ( Object anObject )
1265     {
1266         if ( ( anObject == null ) ||
1267              ( indexOf( displayedObjects, anObject )
1268                == NSArray.NotFound ) )
1269         {
1270             clearSelection();
1271             return false;
1272         }
1273 
1274         selectObjectsIdenticalTo( new NSArray( new Object[] { anObject } ) );
1275         return true;
1276     }
1277 
1278     /***
1279     * Sets the selection to the specified objects.
1280     * If the specified list is null or if none of the objects
1281     * in the list exist in the list of displayed objects, the
1282     * selection will be cleared.
1283     * @return true if all specified objects were selected.
1284     */
1285     public boolean selectObjectsIdenticalTo ( List anObjectList )
1286     {
1287         // optimization: check for resetting of selection
1288         if ( ( anObjectList != null ) && ( selectedObjects.size() == anObjectList.size() ) )
1289         {
1290             boolean identical = true;
1291             int size = selectedObjects.size();
1292             for ( int i = 0; ( i < size ) && identical; i++ )
1293             {
1294                 // compare by reference
1295                 if ( anObjectList.get( i ) != selectedObjects.get( i ) )
1296                 {
1297                     identical = false;
1298                 }
1299                 else if ( displayedObjects.indexOfIdenticalObject(
1300                       anObjectList.get( i ) ) == NSArray.NotFound )
1301                 {
1302                     identical = false;
1303                 }
1304             }
1305             if ( identical )
1306             {
1307                 return true;
1308             }
1309         }
1310 
1311         Object result = notifyDelegate(
1312             "displayGroupShouldChangeSelection",
1313             new Class[] { WODisplayGroup.class, List.class },
1314             new Object[] { this, anObjectList } );
1315         if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
1316         {
1317             // need to notify the calling component
1318             //   to revert back to the previous selection
1319             selectionChanged = true;
1320             willChange();
1321             return false;
1322         }
1323 
1324         int i;
1325         selectionChanged = true;
1326         willChange();
1327         Object o;
1328         selectedObjects.removeAllObjects();
1329         selectedIndexes.removeAllObjects();
1330         Iterator it = anObjectList.iterator();
1331         while ( it.hasNext() )
1332         {
1333             o = it.next();
1334             if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
1335                 != NSArray.NotFound )
1336             {
1337                 selectedObjects.addObject( o );
1338                 selectedIndexes.addObject( new Integer( i ) );
1339             }
1340         }
1341         
1342         notifyDelegate(
1343             "displayGroupDidChangeSelection",
1344             new Class[] { WODisplayGroup.class },
1345             new Object[] { this } );
1346         notifyDelegate(
1347             "displayGroupDidChangeSelectedObjects",
1348             new Class[] { WODisplayGroup.class },
1349             new Object[] { this } );
1350 
1351         return true;
1352     }
1353     
1354     /***
1355     * Calls selectObjectsIdenticalTo and if false is returned
1356     * and selectFirstIfNoMatch is true, selects the first object.
1357     */
1358     public boolean selectObjectsIdenticalToSelectFirstOnNoMatch(
1359         List anObjectList, boolean selectFirstIfNoMatch )
1360     {
1361         if ( selectObjectsIdenticalTo( anObjectList ) ) 
1362         {
1363             return true;
1364         }
1365         if ( selectFirstIfNoMatch )
1366         {
1367             clearSelection();
1368             selectNext();
1369             return true;
1370         }
1371         return false;
1372     }
1373 
1374     /***
1375     * Sets the selection to the previous displayed object before the current
1376     * selection.  If the first object is selected, or if no object
1377     * is selected, then the last object becomes selected.
1378     * If multiple items are selected, the first selected item is
1379     * considered the selected item for the purposes of this method.
1380     * Does not call redisplay().
1381     * @return null to force a page reload.
1382     */
1383     public Object selectPrevious()
1384     {
1385         int i = displayedObjects.count();
1386         if ( i == 0 ) return null;
1387         if ( i == 1 )
1388         {
1389             selectObject( displayedObjects.objectAtIndex( 0 ) );
1390             return null;
1391         }
1392 
1393         Object selectedObject = selectedObject();
1394         if ( selectedObject != null )
1395         {
1396             i = indexOf( displayedObjects, selectedObject );
1397         }
1398         if ( i == NSArray.NotFound ) i = displayedObjects.count();
1399 
1400         // select next object
1401         i--;
1402         if ( i < 0 )
1403         {
1404             // out of range - select last object
1405             i = displayedObjects.count() - 1;
1406         }
1407 
1408         selectObject( displayedObjects.objectAtIndex( i ) );
1409         return null;
1410     }
1411 
1412     /***
1413     * Returns the currently selected object, or null if
1414     * there is no selection.
1415     */
1416     public Object selectedObject()
1417     {
1418         if ( selectedObjects.count() == 0 )
1419         {
1420             return null;
1421         }
1422         return selectedObjects.objectAtIndex( 0 );
1423     }
1424 
1425     /***
1426     * Returns a read-only List containing all selected objects, if any.
1427     * Returns an empty list if no objects are selected.
1428     */
1429     public NSArray selectedObjects()
1430     { // System.out.println( "avoided allocation: selectedObjects" );
1431         return selectedObjectsProxy;
1432     }
1433 
1434     /***
1435     * Returns a read-only List containing the indexes of all selected
1436     * objects, if any.  The list contains instances of
1437     * java.lang.Number; call intValue() to retrieve the index.
1438     */
1439     public NSArray selectionIndexes()
1440     {
1441 //        return selectedIndexes;
1442         int i;
1443         NSMutableArray result = new NSMutableArray();
1444         Enumeration e = selectedObjects.objectEnumerator();
1445         while ( e.hasMoreElements() )
1446         {
1447             i = indexOf( displayedObjects, e.nextElement() );
1448             if ( i != NSArray.NotFound )
1449             {
1450                 result.addObject( new Integer( i ) );
1451             }
1452             else
1453             {
1454               System.err.println( 
1455                   "Should never happen: selected objects not in displayed objects" );
1456               new RuntimeException().printStackTrace( System.err );
1457             }
1458         }
1459         return result;
1460     }
1461 
1462     /***
1463     * Sets the objects managed by this display group.
1464     * updateDisplayedObjects() is called to filter the
1465     * display objects.  The previous selection will be
1466     * maintained if possible.  The data source is not
1467     * notified.
1468     */
1469     public void setObjectArray ( List anObjectList )
1470     {
1471         if ( anObjectList == null ) anObjectList = new NSArray();
1472 
1473         Object result = notifyDelegate(
1474             "displayGroupDisplayArrayForObjects",
1475             new Class[] { WODisplayGroup.class, List.class },
1476             new Object[] { this, anObjectList } );
1477         if ( result != null )
1478         {
1479             anObjectList = (List) result;
1480         }
1481 
1482         willChange();
1483 
1484         NSArray oldSelectedObjects = new NSArray( selectedObjects ); // copy
1485 
1486         // reset allObjects to new list
1487         allObjects.removeAllObjects();
1488         allObjects.addObjectsFromArray( anObjectList );
1489 
1490         // update the displayed object list
1491         updateDisplayedObjects();
1492         
1493         // restore the selection if possible
1494         selectObjectsIdenticalTo( oldSelectedObjects );
1495 
1496         batchIndex = 0;        
1497         displayBatchContainingSelectedObject();
1498     }
1499 
1500     /***
1501     * Sets the currently selected object, or clears the
1502     * selection if the object is not found or is null.
1503     * Note: it's not clear how this differs from
1504     * selectObject in the spec.  It is recommended that
1505     * you call selectObject for now.
1506     */
1507     public void setSelectedObject ( Object anObject )
1508     {
1509         selectObject( anObject );
1510     }
1511 
1512     /***
1513     * Sets the current selection to the specified objects.
1514     * The previous selection is cleared, and any objects
1515     * in the display group that are in the specified list
1516     * are then selected.  If no items in the specified list
1517     * are found in the display group, then the selection is
1518     * effectively cleared.
1519     * Note: it's not clear how this differs from
1520     * selectObjectsIdenticalTo in the spec.
1521     * It is recommended that you call that method for now.
1522     */
1523     public void setSelectedObjects ( List aList )
1524     {
1525         selectObjectsIdenticalTo( aList );
1526     }
1527 
1528     /***
1529     * Sets the current selection to the objects at the
1530     * specified indexes.  Items in the list are assumed
1531     * to be instances of java.lang.Number.
1532     * The previous selection is cleared, and any objects
1533     * in the display group that are in the specified list
1534     * are then selected.  If no items in the specified list
1535     * are found in the display group, then the selection is
1536     * effectively cleared.
1537     */
1538     public boolean setSelectionIndexes ( List aList )
1539     {
1540         Object o;
1541         int index;
1542         NSMutableArray objects = new NSMutableArray();
1543         Iterator it = aList.iterator();
1544         while ( it.hasNext() )
1545         {
1546             index = ((Number)it.next()).intValue();
1547             if ( index < displayedObjects.count() )
1548             {
1549                 o = displayedObjects.objectAtIndex( index );
1550                 if ( o != null )
1551                 {
1552                     objects.add( o );
1553                 }
1554             }
1555         }
1556         return selectObjectsIdenticalTo( objects );
1557     }
1558 
1559     /***
1560     * Applies the qualifier to all objects and sorts
1561     * the results to update the list of displayed objects.
1562     * Observing associations are notified to reflect the changes.
1563     */
1564     public void updateDisplayedObjects()
1565     {
1566         updatedObjectIndex = -1;
1567         willChange();
1568 
1569         displayedObjects.removeAllObjects();
1570 
1571         displayedObjects.addObjectsFromArray( allObjects );
1572 
1573         // apply qualifier, if any
1574         if ( qualifier != null )
1575         {
1576             EOQualifier.filterArrayWithQualifier(
1577                 displayedObjects, qualifier );
1578         }
1579 
1580         // apply sort orderings, if any
1581         NSArray orderings = sortOrderings();
1582         if ( orderings != null )
1583         {
1584             if ( orderings.count() > 0 )
1585             {
1586                 selectionChanged = true;
1587                 willChange();
1588                 EOSortOrdering.sortArrayUsingKeyOrderArray(
1589                     displayedObjects, orderings );
1590             }
1591         }
1592 
1593           // make sure the selectedObjects is a subset of displayedObjects
1594           int i;
1595           Object o;
1596           Iterator it = new LinkedList( selectedObjects ).iterator();
1597           boolean removeflag = false;
1598           selectedIndexes.removeAllObjects();
1599           while ( it.hasNext() )
1600           {
1601               o = it.next();
1602               if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
1603                       == NSArray.NotFound )
1604               {
1605                   selectedObjects.removeIdenticalObject( o );
1606                   removeflag = true;
1607               }
1608               else
1609               {
1610                   selectedIndexes.addObject( new Integer( i ) );
1611               }
1612           }
1613 
1614           //Note: it is important to put the
1615           //selectionChanged = true line below remove.
1616           if (removeflag)
1617           {
1618             selectionChanged = true;
1619             willChange();
1620 
1621             notifyDelegate(
1622                 "displayGroupDidChangeSelection",
1623                 new Class[] { WODisplayGroup.class },
1624                 new Object[] { this } );
1625             notifyDelegate(
1626                 "displayGroupDidChangeSelectedObjects",
1627                 new Class[] { WODisplayGroup.class },
1628                 new Object[] { this } );
1629           }
1630     }
1631 
1632     /***
1633     * Returns the index of the changed object.  If more than
1634     * one object has changed, -1 is returned.
1635     */
1636     public int updatedObjectIndex()
1637     {
1638         return updatedObjectIndex;
1639     }
1640 
1641     // getting and setting values in objects
1642 
1643     /***
1644     * Returns a value on the selected object for the specified key.
1645     */
1646     public Object selectedObjectValueForKey ( String aKey )
1647     {
1648         Object selectedObject = selectedObject();
1649         if ( selectedObject == null ) return null;
1650         return valueForObject( selectedObject, aKey );
1651     }
1652 
1653     /***
1654     * Sets the specified value for the specified key on
1655     * all selected objects.
1656     */
1657     public boolean setSelectedObjectValue (
1658         Object aValue, String aKey )
1659     {
1660         Object selectedObject = selectedObject();
1661         if ( selectedObject == null ) return false;
1662         return setValueForObject( aValue, selectedObject, aKey );
1663     }
1664 
1665     /***
1666     * Sets the specified value for the specified key on
1667     * the specified object.  Validations may be triggered,
1668     * and error dialogs may appear to the user.
1669     * @return True if the value was set successfully,
1670     * false if the value could not be set and the update
1671     * operation should not continue.
1672     */
1673     public boolean setValueForObject (
1674         Object aValue, Object anObject, String aKey )
1675     {
1676         // notify object's observers:
1677         //   this includes us, and will notify our observers
1678         EOObserverCenter.notifyObserversObjectWillChange( anObject );
1679 
1680         //TODO: if key is null, need to remove old object
1681         // and add new object instead of simply replacing it.
1682 
1683         try
1684         {
1685             if ( anObject instanceof EOKeyValueCoding )
1686             {
1687                 ((EOKeyValueCoding)anObject).takeValueForKey( aValue, aKey );
1688             }
1689             else
1690             {
1691                 EOKeyValueCodingSupport.takeValueForKey( anObject, aValue, aKey );
1692             }
1693         }
1694         catch ( RuntimeException exc )
1695         {
1696             Object result = notifyDelegate(
1697                 "displayGroupShouldDisplayAlert",
1698                 new Class[] { WODisplayGroup.class, String.class, String.class },
1699                 new Object[] { this, "Error", exc.getMessage() } );
1700             if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1701             {
1702                 throw exc;
1703             }
1704             return false;
1705         }
1706 
1707         notifyDelegate(
1708             "displayGroupDidSetValueForObject",
1709             new Class[] { WODisplayGroup.class, Object.class, Object.class, String.class },
1710             new Object[] { this, aValue, anObject, aKey } );
1711 
1712         return true;
1713     }
1714 
1715     /***
1716     * Calls setValueForObject() for the object at
1717     * the specified index.
1718     */
1719     public boolean setValueForObjectAtIndex (
1720         Object aValue, int anIndex, String aKey )
1721     {
1722         return setValueForObject(
1723             aValue, displayedObjects.objectAtIndex( anIndex ), aKey );
1724     }
1725 
1726     /***
1727     * Returns the value for the specified key on the specified object.
1728     */
1729     public Object valueForObject ( Object anObject, String aKey )
1730     {
1731         // empty string is considered the identity property
1732         if ( aKey == null ) return anObject;
1733         if ( aKey.equals( "" ) ) return anObject;
1734 
1735         try
1736         {
1737             if ( anObject instanceof EOKeyValueCoding )
1738             {
1739                 return ((EOKeyValueCoding)anObject).valueForKey( aKey );
1740             }
1741             else
1742             {
1743                 return EOKeyValueCodingSupport.valueForKey( anObject, aKey );
1744             }
1745         }
1746         catch ( RuntimeException exc )
1747         {
1748             Object result = notifyDelegate(
1749                 "displayGroupShouldDisplayAlert",
1750                 new Class[] { WODisplayGroup.class, String.class, String.class },
1751                 new Object[] { this, "Error", exc.getMessage() } );
1752             if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1753             {
1754                 throw exc;
1755             }
1756             return null;
1757         }
1758     }
1759 
1760     /***
1761     * Calls valueForObject() for the object at the specified index.
1762     * Returns null if out of bounds.
1763     */
1764     public Object valueForObjectAtIndex ( int anIndex, String aKey )
1765     {
1766         if ( displayedObjects.count() <= anIndex ) return null;
1767         Object o = displayedObjects.objectAtIndex( anIndex );
1768         return valueForObject( o, aKey );
1769     }
1770 
1771     /***
1772     * Prints out the list of displayed objects.
1773     */
1774     public String toString()
1775     {
1776         return displayedObjects.toString();
1777     }
1778 
1779 
1780     /***
1781     * Handles notifications from the data source's editing context,
1782     * looking for InvalidatedAllObjectsInStoreNotification and
1783     * ObjectsChangedInEditingContextNotification, refetching in
1784     * the former case and updating displayed objects in the latter.
1785     * Note: This method is not in the public specification.
1786     */
1787     public void objectsInvalidatedInEditingContext( NSNotification aNotification )
1788     {
1789         if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification
1790             .equals( aNotification.name() ) )
1791         {
1792             Object result = notifyDelegate(
1793                 "displayGroupShouldRefetch",
1794                 new Class[] { WODisplayGroup.class, NSNotification.class },
1795                 new Object[] { this, aNotification } );
1796             if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1797             {
1798                 fetch();
1799             }
1800         }
1801         else
1802         if ( EOEditingContext.ObjectsChangedInEditingContextNotification
1803             .equals( aNotification.name() ) )
1804         {
1805             Object result = notifyDelegate(
1806                 "displayGroupShouldRedisplay",
1807                 new Class[] { WODisplayGroup.class, NSNotification.class },
1808                 new Object[] { this, aNotification } );
1809             if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
1810             {
1811                 int index;
1812                 Enumeration e;
1813                 boolean didChange = false;
1814                 NSDictionary userInfo = aNotification.userInfo();
1815     
1816                 // inserts are ignored
1817     
1818                 // mark updated objects as updated
1819                 NSArray updates = (NSArray) userInfo.objectForKey(
1820                     EOObjectStore.UpdatedKey );
1821                 e = updates.objectEnumerator();
1822                 while ( e.hasMoreElements() )
1823                 {
1824                     index = indexOf( displayedObjects, e.nextElement() );
1825                     if ( index != NSArray.NotFound )
1826                     {
1827                         //System.out.println( "WODisplayGroup: updated: " + index );
1828                         if ( ! didChange )
1829                         {
1830                             didChange = true;
1831                             willChange();
1832                             updatedObjectIndex = index;
1833                         }
1834                         else
1835                         {
1836                             updatedObjectIndex = -1;
1837                         }
1838                     }
1839                 }
1840     
1841                 // treat invalidated objects as updated
1842                 NSArray invalidates = (NSArray) userInfo.objectForKey(
1843                     EOObjectStore.InvalidatedKey );
1844                 e = invalidates.objectEnumerator();
1845                 while ( e.hasMoreElements() )
1846                 {
1847                     index = indexOf( displayedObjects, e.nextElement() );
1848                     if ( index != NSArray.NotFound )
1849                     {
1850                         //System.out.println( "WODisplayGroup: invalidated: " + index );
1851                         if ( ! didChange )
1852                         {
1853                             didChange = true;
1854                             willChange();
1855                             updatedObjectIndex = index;
1856                         }
1857                         else
1858                         {
1859                             updatedObjectIndex = -1;
1860                         }
1861                     }
1862                 }
1863     
1864                 // remove deletes from display group if they exist
1865                 NSArray deletes = (NSArray) userInfo.objectForKey(
1866                     EOObjectStore.DeletedKey );
1867                 e = deletes.objectEnumerator();
1868                 Object o;
1869                 while ( e.hasMoreElements() )
1870                 {
1871                     o = e.nextElement();
1872                     index = indexOf( displayedObjects, o );
1873                     if ( index != NSArray.NotFound )
1874                     {
1875                         //System.out.println( "WODisplayGroup: deleted: " + o );
1876                         deleteObjectAtIndexNoNotify( index );
1877                     }
1878                 }
1879                 
1880                 if ( !usesOptimisticRefresh() )
1881                 {
1882                     updateDisplayedObjects();
1883                 }
1884             }
1885         }
1886 
1887     }
1888 
1889     // static methods
1890 
1891     /***
1892     * Specifies the default behavior for whether changes
1893     * should be validated immediately for all display groups.
1894     */
1895     public static boolean
1896         globalDefaultForValidatesChangesImmediately()
1897     {
1898         return globalDefaultForValidatesChangesImmediately;
1899     }
1900 
1901     /***
1902     * Specifies the default string matching format for all
1903     * display groups.
1904     */
1905     public static String globalDefaultStringMatchFormat()
1906     {
1907         return globalDefaultStringMatchFormat;
1908     }
1909 
1910     /***
1911     * Specifies the default string matching operator for all
1912     * display groups.
1913     */
1914     public static String globalDefaultStringMatchOperator()
1915     {
1916         return globalDefaultStringMatchOperator;
1917     }
1918 
1919     /***
1920     * Sets the default behavior for validating changes
1921     * for all display groups.
1922     */
1923     public static void
1924     setGlobalDefaultForValidatesChangesImmediately (
1925         boolean validatesImmediately )
1926     {
1927         globalDefaultForValidatesChangesImmediately =
1928             validatesImmediately;
1929     }
1930 
1931     /***
1932     * Sets the default string matching format that
1933     * will be used by all display groups.
1934     */
1935     public static void
1936     setGlobalDefaultStringMatchFormat ( String aFormat )
1937     {
1938         globalDefaultStringMatchFormat = aFormat;
1939     }
1940 
1941     /***
1942     * Sets the default string matching operator that
1943     * will be used by all display groups.
1944     */
1945     public static void
1946     setGlobalDefaultStringMatchOperator ( String anOperator )
1947     {
1948         globalDefaultStringMatchOperator = anOperator;
1949     }
1950 
1951     /***
1952     * Needed because we don't inherit from NSObject.
1953     * Calls EOObserverCenter.notifyObserversObjectWillChange.
1954     */
1955     protected void willChange()
1956     {
1957         EOObserverCenter.notifyObserversObjectWillChange( this );
1958     }
1959 
1960     /***
1961     * Returns the index of the specified object in the
1962     * specified NSArray, comparing by value or by reference
1963     * as determined by the private instance variable
1964     * compareByReference.  If not found, returns NSArray.NotFound.
1965     */
1966     private int indexOf( NSArray anArray, Object anObject )
1967     {
1968         if ( compareByReference )
1969         {
1970             return anArray.indexOfIdenticalObject( anObject );
1971         }
1972         else
1973         {
1974             return anArray.indexOf( anObject );
1975         }
1976     }
1977 
1978     // interface EOObserving
1979 
1980     /***
1981     * Receives notifications of changes from objects that
1982     * are managed by this display group.  This implementation
1983     * sets updatedObjectIndex as appropriate.
1984     */
1985     public void objectWillChange(Object anObject)
1986     {
1987         int index = indexOf( displayedObjects, anObject );
1988         if ( index != NSArray.NotFound )
1989         {
1990             updatedObjectIndex = index;
1991             willChange();
1992         }
1993     }
1994 
1995     // interface EOEditingContext.Editor
1996     
1997     /***
1998     * Called before the editing context begins to save changes.
1999     * This implementation calls endEditing().
2000     */
2001     public void editingContextWillSaveChanges( 
2002         EOEditingContext anEditingContext )
2003     {
2004         endEditing();
2005     }
2006 
2007     /***
2008     * Called to determine whether this editor has changes
2009     * that have not been committed to the object in the context.
2010     * This implementation returns false.
2011     */
2012     public boolean editorHasChangesForEditingContext(
2013         EOEditingContext anEditingContext )
2014     {
2015         return false;
2016     }
2017 
2018     // interface EOEditingContext.MessageHandler
2019     
2020     /***
2021     * Called to display a message for an error that occurred
2022     * in the specified editing context.  If the delegate allows,
2023     * this implementation writes a message to the standard output.
2024     * Override to customize.
2025     */
2026     public void editingContextPresentErrorMessage( 
2027         EOEditingContext anEditingContext,
2028         String aMessage )
2029     {
2030         Object result = notifyDelegate(
2031             "displayGroupShouldDisplayAlert",
2032             new Class[] { WODisplayGroup.class, String.class, String.class },
2033             new Object[] { this, "Error", aMessage } );
2034         if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
2035         {
2036             System.out.println( aMessage );
2037         }
2038     }
2039 
2040     /***
2041     * Called by the specified object store to determine whether
2042     * fetching should continue, where count is the current count
2043     * and limit is the limit as specified by the fetch specification.
2044     * This implementation returns true.  Override to customize.
2045     */
2046     public boolean editingContextShouldContinueFetching(
2047         EOEditingContext anEditingContext,
2048         int count,
2049         int limit,
2050         EOObjectStore anObjectStore )
2051     {
2052         return true;
2053     }
2054 
2055     /***
2056      * Sends the specified message to the delegate.
2057      * Returns the return value of the method,
2058      * or null if no return value or no delegate
2059      * or no implementation.
2060      */
2061     private Object notifyDelegate( 
2062         String aMethodName, Class[] types, Object[] params )
2063     {
2064         try
2065         {
2066             Object delegate = delegate();
2067             if ( delegate == null ) return null;
2068             return NSSelector.invoke( 
2069                 aMethodName, types, delegate, params );
2070         }
2071         catch ( NoSuchMethodException e )
2072         {
2073             // ignore: not implemented
2074         }
2075         catch ( Exception exc )
2076         {
2077             // log to standard error
2078             System.err.println( 
2079                 "Error while messaging delegate: " + 
2080                     delegate + " : " + aMethodName );
2081             exc.printStackTrace();
2082         }
2083         
2084         return null;
2085     }
2086     
2087     /***
2088     * DisplayGroups can delegate important decisions to a Delegate.  
2089     * Note that DisplayGroup doesn't require its delegates to implement
2090     * this interface: rather, this interface defines the methods that
2091     * DisplayGroup will attempt to invoke dynamically on its delegate.
2092     * The delegate may choose to implement only a subset of the methods
2093     * on the interface.
2094     */
2095     public interface Delegate
2096     {
2097         /***
2098         * Called when the specified data source fails
2099         * to create an object for the specified display group.
2100         */
2101         void displayGroupCreateObjectFailed (
2102             WODisplayGroup aDisplayGroup,
2103             EODataSource aDataSource );
2104 
2105         /***
2106         * Called after the specified display group's
2107         * data source is changed.
2108         */
2109         void displayGroupDidChangeDataSource (
2110             WODisplayGroup aDisplayGroup );
2111 
2112         /***
2113         * Called after a change occurs in the specified 
2114         * display group's selected objects.
2115         */
2116         void displayGroupDidChangeSelectedObjects (
2117             WODisplayGroup aDisplayGroup );
2118 
2119         /***
2120         * Called after the specified display group's
2121         * selection has changed.
2122         */
2123         void displayGroupDidChangeSelection (
2124             WODisplayGroup aDisplayGroup );
2125 
2126         /***
2127         * Called after the specified display group has
2128         * deleted the specified object.
2129         */
2130         void displayGroupDidDeleteObject (
2131             WODisplayGroup aDisplayGroup,
2132             Object anObject );
2133 
2134         /***
2135         * Called after the specified display group
2136         * has fetched the specified object list.
2137         */
2138         void displayGroupDidFetchObjects (
2139             WODisplayGroup aDisplayGroup,
2140             List anObjectList );
2141 
2142         /***
2143         * Called after the specified display group
2144         * has inserted the specified object into
2145         * its internal object list.
2146         */
2147         void displayGroupDidInsertObject (
2148             WODisplayGroup aDisplayGroup,
2149             Object anObject );
2150 
2151         /***
2152         * Called after the specified display group
2153         * has set the specified value for the specified
2154         * object and key.
2155         */
2156         void displayGroupDidSetValueForObject (
2157             WODisplayGroup aDisplayGroup,
2158             Object aValue,
2159             Object anObject,
2160             String aKey );
2161 
2162         /***
2163         * Called by the specified display group to
2164         * determine what objects should be displayed
2165         * for the objects in the specified list.
2166         * @return An NSArray containing the objects
2167         * to be displayed for the objects in the
2168         * specified list.
2169         */
2170         NSArray displayGroupDisplayArrayForObjects (
2171             WODisplayGroup aDisplayGroup,
2172             List aList );
2173 
2174         /***
2175         * Called by the specified display group before
2176         * it attempts to change the selection.
2177         * @return True to allow the selection to change,
2178         * false otherwise.
2179         */
2180         boolean displayGroupShouldChangeSelection (
2181             WODisplayGroup aDisplayGroup,
2182             List aSelectionList );
2183 
2184         /***
2185         * Called by the specified display group before
2186         * it attempts to delete the specified object.
2187         * @return True to allow the object to be deleted
2188         * false to prevent the deletion.
2189         */
2190         boolean displayGroupShouldDeleteObject (
2191             WODisplayGroup aDisplayGroup,
2192             Object anObject );
2193 
2194         /***
2195         * Called by the specified display group before
2196         * it attempts display the specified alert to
2197         * the user.
2198         * @return True to allow the message to be
2199         * displayed, false if you want to handle the
2200         * alert yourself and suppress the display group's
2201         * notification.
2202         */
2203         boolean displayGroupShouldDisplayAlert (
2204             WODisplayGroup aDisplayGroup,
2205             String aTitle,
2206             String aMessage );
2207 
2208         /***
2209         * Called by the specified display group before
2210         * it attempts fetch objects.
2211         * @return True to allow the fetch to take place,
2212         * false to prevent the fetch.
2213         */
2214         boolean displayGroupShouldFetch (
2215             WODisplayGroup aDisplayGroup );
2216 
2217         /***
2218         * Called by the specified display group before
2219         * it attempts to insert the specified object.
2220         * @return True to allow the object to be inserted
2221         * false to prevent the insertion.
2222         */
2223         boolean displayGroupShouldInsertObject (
2224             WODisplayGroup aDisplayGroup,
2225             Object anObject,
2226             int anIndex );
2227 
2228         /***
2229         * Called by the specified display group when
2230         * it receives the specified 
2231         * ObjectsChangedInEditingContextNotification.
2232         * @return True to allow the display group to
2233         * update the display (recommended), false
2234         * to prevent the update.
2235         */
2236         boolean displayGroupShouldRedisplay (
2237             WODisplayGroup aDisplayGroup,
2238             NSNotification aNotification );
2239 
2240         /***
2241         * Called by the specified display group when
2242         * it receives the specified 
2243         * InvalidatedAllObjectsInStoreNotification.
2244         * @return True to allow the display group to
2245         * refetch (recommended), false to prevent the 
2246         * refetch.
2247         */
2248         boolean displayGroupShouldRefetch (
2249             WODisplayGroup aDisplayGroup,
2250             NSNotification aNotification );
2251 
2252     }
2253 
2254 }
2255 
2256 /*
2257  * $Log$
2258  * Revision 1.2  2006/02/19 01:44:02  cgruber
2259  * Add xmlrpc files
2260  * Remove jclark and replace with dom4j and javax.xml.sax stuff
2261  * Re-work dependencies and imports so it all compiles.
2262  *
2263  * Revision 1.1  2006/02/16 13:22:22  cgruber
2264  * Check in all sources in eclipse-friendly maven-enabled packages.
2265  *
2266  * Revision 1.6  2003/08/07 00:15:15  chochos
2267  * general cleanup (mostly removing unused imports)
2268  *
2269  * Revision 1.5  2003/01/22 23:01:06  mpowers
2270  * Better handling for index out of bounds.
2271  *
2272  * Revision 1.4  2003/01/21 22:27:02  mpowers
2273  * Corrected context id usage.
2274  * Implemented backtracking.
2275  *
2276  * Revision 1.3  2003/01/21 17:54:01  mpowers
2277  * Batch indices are one-based, not zero-based.
2278  *
2279  * Revision 1.2  2003/01/21 14:38:42  mpowers
2280  * Fixed batching.
2281  *
2282  * Revision 1.1  2003/01/18 23:30:42  mpowers
2283  * WODisplayGroup now compiles.
2284  *
2285  * Revision 1.46  2002/10/24 21:15:36  mpowers
2286  * New implementations of NSArray and subclasses.
2287  *
2288  * Revision 1.45  2002/10/24 18:20:20  mpowers
2289  * Because NSArray is read-only, we are returning our internal representations
2290  * to callers of allObjects(), displayedObjects(), and selectedObjects().
2291  *
2292  * Revision 1.44  2002/08/06 18:20:25  mpowers
2293  * Now posting DisplayGroupWillFetch notifications before fetch.
2294  * Implemented support for usesOptimisticRefresh.
2295  * No longer supporting inserted/updated/deleted lists: not part of spec.
2296  *
2297  * Revision 1.43  2002/05/17 15:01:49  mpowers
2298  * Implemented dynamic lookup of delegate methods so delegates no longer
2299  * need to implement the DisplayGroup.Delegate interface.
2300  *
2301  * Revision 1.42  2002/03/26 21:46:06  mpowers
2302  * Contributing EditingContext as a java-friendly convenience.
2303  *
2304  * Revision 1.41  2002/03/11 03:17:56  mpowers
2305  * Provided control point for coalesced changes.
2306  *
2307  * Revision 1.40  2002/03/05 23:18:28  mpowers
2308  * Added documentation.
2309  * Added isSelectionPaintedImmediate and isSelectionTracking attributes
2310  * to TableAssociation.
2311  * Added getTableAssociation to TableColumnAssociation.
2312  *
2313  * Revision 1.39  2002/02/19 22:26:04  mpowers
2314  * Implemented EOEditingContext.MessageHandler support.
2315  *
2316  * Revision 1.38  2002/02/19 16:37:38  mpowers
2317  * Implemented support for EOEditingContext.Editor
2318  *
2319  * Revision 1.37  2001/12/11 22:17:48  mpowers
2320  * Now properly handling exceptions in valueForObject.
2321  * No longer trying to retain selection based only on index.
2322  *
2323  * Revision 1.36  2001/11/08 21:42:00  mpowers
2324  * Now we know what to do with shouldRefetch and shouldRedisplay.
2325  *
2326  * Revision 1.35  2001/11/04 18:26:58  mpowers
2327  * Fixed bug where exceptions were not properly reported when updating
2328  * a value and the display group did not have a delegate.
2329  *
2330  * Revision 1.34  2001/11/02 20:59:36  mpowers
2331  * Now correctly ensuring selected objects are a subset of displayed objects.
2332  *
2333  * Revision 1.33  2001/10/30 22:56:45  mpowers
2334  * Added support for EOQualifier.
2335  *
2336  * Revision 1.32  2001/10/23 22:27:53  mpowers
2337  * Now running at ObserverPrioritySixth.
2338  *
2339  * Revision 1.31  2001/10/23 18:45:05  mpowers
2340  * Rolling back changes.
2341  *
2342  * Revision 1.28  2001/08/22 19:23:41  mpowers
2343  * No longer asserting objects in all objects list.
2344  *
2345  * Revision 1.27  2001/07/30 16:17:01  mpowers
2346  * Minor code cleanup.
2347  *
2348  * Revision 1.26  2001/07/10 22:49:07  mpowers
2349  * Fixed bug in optimization for selectObjectsIdenticalTo (found by Dongzhi).
2350  *
2351  * Revision 1.25  2001/06/19 15:40:21  mpowers
2352  * Now only changing the selection if the new selection is different
2353  * from the old.
2354  *
2355  * Revision 1.24  2001/05/24 17:36:15  mpowers
2356  * Fixed problem with selectedObjectsIdenticalTo: it was using compare
2357  * by value instead of compare by reference.
2358  *
2359  * Revision 1.23  2001/05/18 21:09:19  mpowers
2360  * Now throwing exceptions if the delegate cannot handle error from update.
2361  *
2362  * Revision 1.22  2001/05/14 15:26:12  mpowers
2363  * Now checking for null delegate before and after selection change.
2364  *
2365  * Revision 1.21  2001/05/08 18:47:34  mpowers
2366  * Minor fixes for d3.
2367  *
2368  * Revision 1.20  2001/04/29 22:02:45  mpowers
2369  * Work on id transposing between editing contexts.
2370  *
2371  * Revision 1.19  2001/04/13 16:38:09  mpowers
2372  * Alpha3 release.
2373  *
2374  * Revision 1.18  2001/04/03 20:36:01  mpowers
2375  * Fixed refaulting/reverting/invalidating to be self-consistent.
2376  *
2377  * Revision 1.17  2001/03/29 03:31:13  mpowers
2378  * No longer using Introspector.
2379  *
2380  * Revision 1.16  2001/02/27 03:32:18  mpowers
2381  * Implemented default values for new objects.
2382  *
2383  * Revision 1.15  2001/02/27 02:11:17  mpowers
2384  * Now throwing exception when cloning fails.
2385  * Removed debugging printlns.
2386  *
2387  * Revision 1.14  2001/02/26 22:41:51  mpowers
2388  * Implemented null placeholder classes.
2389  * Duplicator now uses NSNull.
2390  * No longer catching base exception class.
2391  *
2392  * Revision 1.13  2001/02/26 15:53:22  mpowers
2393  * Fine-tuning notification firing.
2394  * Child display groups now update properly after parent save or invalidate.
2395  *
2396  * Revision 1.12  2001/02/22 20:55:06  mpowers
2397  * Implemented notification handling.
2398  *
2399  * Revision 1.11  2001/02/21 20:40:42  mpowers
2400  * setObjectArray now falls back to index when trying to retain the
2401  * same selection.
2402  *
2403  * Revision 1.10  2001/02/20 16:38:55  mpowers
2404  * MasterDetailAssociations now observe their controlled display group's
2405  * objects for changes to that the parent object will be marked as updated.
2406  * Before, only inserts and deletes to an object's items are registered.
2407  * Also, moved ObservableArray to package access.
2408  *
2409  * Revision 1.9  2001/02/17 17:23:49  mpowers
2410  * More changes to support compiling with jdk1.1 collections.
2411  *
2412  * Revision 1.8  2001/02/17 16:52:05  mpowers
2413  * Changes in imports to support building with jdk1.1 collections.
2414  *
2415  * Revision 1.7  2001/01/24 16:35:37  mpowers
2416  * Improved documentation on TreeAssociation.
2417  * SortOrderings are now inherited from parent nodes.
2418  * Updates after sorting are still lost on TreeController.
2419  *
2420  * Revision 1.6  2001/01/24 14:23:05  mpowers
2421  * Added support for OrderedDataSource.
2422  *
2423  * Revision 1.5  2001/01/12 17:21:37  mpowers
2424  * Implicit creation of EOSortOrderings now happens in setSortOrderings.
2425  *
2426  * Revision 1.4  2001/01/11 20:34:26  mpowers
2427  * Implemented EOSortOrdering and added support in framework.
2428  * Added header-click to sort table columns.
2429  *
2430  * Revision 1.3  2001/01/10 22:49:44  mpowers
2431  * Implemented similarly named selection methods instead of
2432  * throwing exceptions.
2433  *
2434  * Revision 1.2  2001/01/09 20:12:52  mpowers
2435  * Moved inner classes to package access.
2436  *
2437  * Revision 1.1.1.1  2000/12/21 15:48:20  mpowers
2438  * Contributing wotonomy.
2439  *
2440  * Revision 1.21  2000/12/20 16:25:39  michael
2441  * Added log to all files.
2442  *
2443  * Revision 1.20  2000/12/15 15:04:42  michael
2444  * Added doc.
2445  *
2446  * Revision 1.19  2000/12/11 13:32:48  michael
2447  * Finish the much better TreeAssociation implementation.
2448  * TreeAssociation now has no gui dependencies.
2449  *
2450  * Revision 1.18  2000/12/05 17:41:46  michael
2451  * Broadcasts selection change after delegate refuses selection change
2452  * so the initiating association gets refreshed.
2453  *
2454  */
2455