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