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.util.Enumeration;
22  
23  import net.wotonomy.control.EODelayedObserver;
24  import net.wotonomy.control.EOObserverCenter;
25  import net.wotonomy.foundation.NSArray;
26  import net.wotonomy.foundation.NSMutableArray;
27  import net.wotonomy.foundation.NSMutableDictionary;
28  
29  /***
30  * Associations observe DisplayGroups and associate
31  * a user interface component with one or more keys 
32  * on the objects in the display group. <br><br>
33  *
34  * Associations are created with a ui component in
35  * the constructor.  Then, one or more aspects are
36  * bound to display groups and/or property keys with
37  * the bindAspect() method.   Finally, the association
38  * is initialized with the establishConnection() 
39  * method. <br><br>
40  *
41  * Per the openstep convention, you do not need to 
42  * retain a reference to the association after it is 
43  * created; the association will be garbage-collected
44  * when the ui component is garbage-collected. <br><br> 
45  *
46  * (Because java components don't have delegates like
47  * openstep components do, java-based associations 
48  * will likely need to implement some sort of listener
49  * and add itself to the component's list of listeners
50  * so that the component will have a strong reference
51  * (and the only reference) to the association.) <br><br>
52  *
53  * @author michael@mpowers.net
54  * @author $Author: cgruber $
55  * @version $Revision: 904 $
56  */
57  public class EOAssociation extends EODelayedObserver
58  {
59  	// aspect constants 
60  	public static final String ActionAspect = "action";
61  	public static final String EnabledAspect = "enabled";
62  	public static final String SourceAspect = "source";
63  	public static final String ArgumentAspect = "argument";
64  	public static final String ParentAspect = "parent";
65  	public static final String TitlesAspect = "titles";
66  	public static final String BoldAspect = "bold";
67  	public static final String SelectedObjectAspect = "selectedObject";
68  	public static final String ValueAspect = "value";
69  	public static final String DestinationAspect = "destination";
70  	public static final String SelectedTitleAspect = "selectedTitle";
71  	public static final String URLAspect = "URL";
72  	public static final String ItalicAspect = "italic";
73  	public static final String ChildrenAspect = "children"; // not in spec
74  	public static final String IsLeafAspect = "isLeaf"; // not in spec
75  	public static final String EditableAspect = "editable"; // not in spec
76  	public static final String VisibleAspect = "visible"; // not in spec
77  	public static final String ObjectsAspect = "objects"; // not in spec
78  	public static final String LabelAspect = "label"; // not in spec
79  	public static final String IconAspect = "icon"; // not in spec
80  		
81  	public static final String AttributeAspectSignature = "A";
82  	public static final String NullAspectSignature = "";
83  	public static final String AttributeToOneAspectSignature = "A1"; 
84  	public static final String ToOneAspectSignature = "1";
85  	public static final String AttributeToOneToManyAspectSignature = "A1M"; 
86  	public static final String ToOneToManyAspectSignature = "1M";
87  	public static final String AttributeToManyAspectSignature = "AM";
88  	public static final String ToManyAspectSignature = "M";
89  		
90      protected Object control;
91      protected NSMutableDictionary aspectToGroup;
92      protected NSMutableDictionary aspectToKey;
93  
94      /***
95      * Default constructor.
96      */ 
97      public EOAssociation ()
98      {
99          aspectToGroup = new NSMutableDictionary();
100         aspectToKey = new NSMutableDictionary();
101         control = null;
102     }
103     
104     /***
105     * Constructor specifying the object to be controlled by this
106     * association.  Does not establish connection.
107     */
108     public EOAssociation ( Object anObject )
109     {
110         this();
111         control = anObject;
112     }
113     
114     /***
115     * Returns a List of aspect signatures whose contents
116     * correspond with the aspects list.  Each element is 
117     * a string whose characters represent a capability of
118     * the corresponding aspect. <ul>
119     * <li>"A" attribute: the aspect can be bound to
120     * an attribute.</li>
121     * <li>"1" to-one: the aspect can be bound to a
122     * property that returns a single object.</li>
123     * <li>"M" to-one: the aspect can be bound to a
124     * property that returns multiple objects.</li>
125     * </ul> 
126     * An empty signature "" means that the aspect can
127     * bind without needing a key.
128     * This implementation returns "A1M" for each
129     * element in the aspects array.
130     */
131     public static NSArray aspectSignatures ()
132     {
133         int size = aspects().count();
134         NSMutableArray result = new NSMutableArray();
135         for ( int i = 0; i < size; i++ )
136         {
137             result.addObject( AttributeToOneToManyAspectSignature );
138         }
139         return result;
140     }
141     
142     /***
143     * Returns a List that describes the aspects supported
144     * by this class.  Each element in the list is the string
145     * name of the aspect.  This implementation returns an
146     * empty list.
147     * 
148     * TODO: Is this static in the WebObjects published API? If not, fix.
149     */
150     public static NSArray aspects ()
151     {
152         return new NSArray();
153     }
154     
155     /***
156     * Returns all registered subclasses of EOAssociation 
157     * for which usableWithObject with the specified object
158     * returns true.  You should only call this method on 
159     * the EOAssociation class.
160     */
161     public static NSArray associationClassesForObject ( 
162         Object anObject )
163     {
164         throw new RuntimeException( "Not implemented yet." );
165     }
166     
167     /***
168     * Returns a List of EOAssociation subclasses that,
169     * for the objects that are usable for this association,
170     * are less suitable than this association.
171     * This implementation returns an empty list.
172     */
173     public static NSArray associationClassesSuperseded ()
174     {
175         return new NSArray();
176     }
177     
178 
179     /***
180     * Binds the specified aspect of this association to the
181     * specified key on the specified display group.
182     */
183     public void bindAspect ( 
184         String anAspect, EODisplayGroup aDisplayGroup, String aKey )
185     {
186 		// unattach old group, if any
187 		EODisplayGroup oldGroup = displayGroupForAspect( anAspect );
188 		if ( oldGroup != null )
189 		{
190 			EOObserverCenter.removeObserver( this, oldGroup );	
191 		}
192 
193 		// attach new group
194 		if ( aDisplayGroup != null )
195 		{
196 			aspectToGroup.setObjectForKey( aDisplayGroup, anAspect );
197 		}
198 		else
199 		{
200 			aspectToGroup.removeObjectForKey( anAspect );	
201 		}
202 		// attach new key
203 		if ( aKey != null )
204 		{
205 			aspectToKey.setObjectForKey( aKey, anAspect );
206 		}
207 		else
208 		{
209 			aspectToKey.removeObjectForKey( anAspect );
210 		}
211     }
212     
213     /***
214     * Breaks the connection between this association and 
215     * its object.  Override to stop listening for events
216     * from the object, but remember to call this method.
217     * This implementation unregisters this association
218 	* from observing each bound display group.
219     */
220     public void breakConnection ()
221     {
222         discardPendingNotification();
223         Enumeration e = aspectToGroup.objectEnumerator();
224 		while ( e.hasMoreElements() )
225 		{
226 			EOObserverCenter.removeObserver( this, e.nextElement() );	
227 		}
228     }
229 
230     /***
231     * Returns whether this association can bind to the
232     * specified display group on the specified key for
233     * the specified aspect.  
234     * This implementation returns false.
235     */
236     public boolean canBindAspect ( 
237         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
238     {
239         return false;
240     }
241     
242     /***
243     * Copies the binding for each aspect in this association
244     * that has a matching aspect in the specified association.
245     */
246     public void copyMatchingBindingsFromAssociation ( 
247         EOAssociation anAssociation )
248     {
249 		//FIXME: this is broken: aspects() returns EOAssociation.aspects()
250 		//NOTE: This is actually quite crazy - should this even be static? -ceg
251         NSMutableArray ourAspects = new NSMutableArray( this.aspects() );
252         NSArray theirAspects = anAssociation.aspects();
253 
254         String aspect;
255         Enumeration e = ourAspects.objectEnumerator();
256         while ( e.hasMoreElements() )
257         {
258             aspect = e.nextElement().toString();
259             if ( theirAspects.containsObject( aspect ) )
260             {
261                 this.bindAspect(
262                 aspect,
263                 anAssociation.displayGroupForAspect( aspect ),
264                 anAssociation.displayGroupKeyForAspect( aspect ) );
265             }
266         }
267     }
268     
269     /***
270     * Returns the display group that is bound the specified
271     * aspect, or null if no display group is currently 
272     * bound to that aspect.
273     */
274     public EODisplayGroup displayGroupForAspect ( String anAspect )
275     {
276         return (EODisplayGroup) aspectToGroup.objectForKey( anAspect );
277     }
278     
279     /***
280     * Returns the key for the display group bound to the
281     * specified aspect, or null if no display group is currently
282     * bound to that aspect.
283     */
284     public String displayGroupKeyForAspect ( String anAspect )
285     {
286         return (String) aspectToKey.objectForKey( anAspect );
287     }
288     
289     /***
290     * The human-readable descriptive name for this association.
291     * This implementation returns the class name.
292     */
293     public String displayName () // was static - can static method get class name?
294     {
295     	String className = getClass().getName();
296 	    int index = className.lastIndexOf( "." );
297 	    if ( index == -1 ) return className;
298 	    return className.substring( index+1 );
299     }
300 
301     /***
302     * Forces this association to cause the object to 
303     * stop editing and validate the user's input.
304     * This implementation returns true.
305     * @return false if there were problems validating,
306     * or true to continue.
307     */
308     public boolean endEditing ()
309     {
310         return true;
311     }
312 
313     /***
314     * Establishes a connection between this association
315     * and the controlled object.  Subclasses should populate
316 	* their controlled object from the display group and begin
317     * listening for events from their controlled object here,
318 	* but remember to call this method.  Any existing value
319 	* in the controlled object will be overwritten.
320     * This implementation registers this assocation to 
321 	* observer changes to each of the bound display groups.
322     */
323     public void establishConnection ()
324     {
325         Enumeration e = aspectToGroup.objectEnumerator();
326 		while ( e.hasMoreElements() )
327 		{
328 			EOObserverCenter.addObserver( this, e.nextElement() );	
329 		}
330     }
331     
332     /***
333     * Returns whether this class can control the specified 
334     * object.  This implementation returns false.
335     */
336     public static boolean isUsableWithObject ( Object anObject )
337     {
338         return false;
339     }
340     
341     /***
342     * Returns the object that is currently controlled by this
343     * association, or null if no object is currently controlled.
344     */
345     public Object object ()
346     {
347         return control;
348     }
349     
350     /***
351     * Returns a List of properties of the controlled object
352     * that are controlled by this class.  For example,
353     * "stringValue", or "selected".
354     * This implementation returns an empty list.
355     */
356     public static NSArray objectKeysTaken ()
357     {
358         return new NSArray();
359     }
360     
361     /***
362     * Returns the aspect that is considered primary
363     * or default.  This is typically "value" or somesuch.
364     * This implementation returns null.
365     */
366     public static String primaryAspect ()
367     {
368         return null;
369     }
370     
371     /***
372     * Writes the specified value for the display group
373     * and key currently bound to the specified aspect.
374     * This implementation calls 
375     * EODisplayGroup.setSelectedObjectValue().
376     * @return True if the value was successfully set,
377     * otherwise false.
378     */
379     public boolean setValueForAspect ( 
380         Object aValue,
381         String anAspect )
382     {
383         EODisplayGroup group = (EODisplayGroup)
384             aspectToGroup.objectForKey( anAspect );
385         if ( group == null ) return false;
386         String key = (String)
387             aspectToKey.objectForKey( anAspect );
388         if ( key == null ) return false;
389         
390         //FIXME: is this the right method to call
391         return group.setSelectedObjectValue( aValue, key );
392     }
393     
394 
395     /***
396     * Writes the specified value for the display group
397     * and key currently bound to the specified aspect,
398     * at the specified index (assuming it's an indexed
399     * property).
400     * This implementation calls 
401     * EODisplayGroup.setValueForObjectAtIndex().
402     * @return True if the value was successfully set,
403     * otherwise false.
404     */
405     public boolean setValueForAspectAtIndex ( 
406         Object aValue,
407         String anAspect,
408         int anIndex )
409     {
410         EODisplayGroup group = (EODisplayGroup)
411             aspectToGroup.objectForKey( anAspect );
412         if ( group == null ) return false;
413         String key = (String)
414             aspectToKey.objectForKey( anAspect );
415         if ( key == null ) return false;
416         
417         //FIXME: is this the right method to call?
418         return group.setValueForObjectAtIndex( aValue, anIndex, key );
419     }
420     
421     /***
422     * Called by subclasses to notify that the user's input
423     * failed validation.  Notifies the display group for
424     * the aspect, returning the result.
425     * This implementation calls 
426     * EODisplayGroup.associationFailedToValidateValue() 
427     * with the display group's selected object.
428     * @return True if the user should be allowed to continue,
429     * meaning that the display group with handle user notification, 
430     * otherwise false meaning the caller should notify the user.
431     */
432     public boolean shouldEndEditing ( 
433         String anAspect,
434         String anInvalidInput,
435         String anErrorDescription )
436     {
437         EODisplayGroup group = (EODisplayGroup)
438             aspectToGroup.objectForKey( anAspect );
439         if ( group == null ) return false;
440         String key = (String)
441             aspectToKey.objectForKey( anAspect );
442         if ( key == null ) return false;
443         return group.associationFailedToValidateValue(
444             this, anInvalidInput, key, 
445             group.selectedObject(), //FIXME: is this correct?
446             anErrorDescription );
447     }
448     
449     /***
450     * Called by subclasses to notify that the user's input
451     * failed validation.  Notifies the display group for
452     * the aspect, returning the result.
453     * This implementation calls 
454     * EODisplayGroup.associationFailedToValidateValue() 
455     * with the object in the display group's displayed
456     * objects array at the specified index.
457     * @return True if the user should be allowed to continue,
458     * meaning that the display group with handle user notification, 
459     * otherwise false meaning the caller should notify the user.
460     */
461     public boolean shouldEndEditingAtIndex ( 
462         String anAspect,
463         String anInvalidInput,
464         String anErrorDescription,
465         int anIndex )
466     {
467         EODisplayGroup group = (EODisplayGroup)
468             aspectToGroup.objectForKey( anAspect );
469         if ( group == null ) return false;
470         String key = (String)
471             aspectToKey.objectForKey( anAspect );
472         if ( key == null ) return false;
473         return group.associationFailedToValidateValue(
474             this, anInvalidInput, key, 
475             group.displayedObjects().objectAtIndex( anIndex ), 
476             //FIXME: is this correct?
477             anErrorDescription );
478     }
479     
480     /***
481     * Called when either the selection or the contents of 
482     * an associated display group have changed.
483     * This implementation does nothing.
484     */
485     public void subjectChanged ()
486     {
487         // does nothing
488     }
489     
490     /***
491     * Returns the current value for the display group 
492     * and key associated with the specified aspect.
493     */
494     public Object valueForAspect ( String anAspect )
495     {
496         EODisplayGroup group = (EODisplayGroup)
497             aspectToGroup.objectForKey( anAspect );
498         if ( group == null ) return null;
499         String key = (String)
500             aspectToKey.objectForKey( anAspect );
501         if ( key == null ) return null;
502         
503         //FIXME: is this correct?  use selected object?
504         return group.valueForObject( group.selectedObject(), key );
505     }
506     
507     /***
508     * Returns the current value for the display group 
509     * and key associated with the specified aspect,
510     * and the specified index (assuming multiple values).
511     */
512     public Object valueForAspectAtIndex ( 
513         String anAspect, int anIndex )
514     {
515         EODisplayGroup group = (EODisplayGroup)
516             aspectToGroup.objectForKey( anAspect );
517         if ( group == null ) return null;
518         String key = (String)
519             aspectToKey.objectForKey( anAspect );
520         if ( key == null ) return null;
521         
522         //FIXME: is this the right method to call?
523         return group.valueForObjectAtIndex( anIndex, key );
524     }
525     
526 }
527 
528 /*
529  * $Log$
530  * Revision 1.2  2006/02/18 23:14:35  cgruber
531  * Update imports and maven dependencies.
532  *
533  * Revision 1.1  2006/02/16 13:22:22  cgruber
534  * Check in all sources in eclipse-friendly maven-enabled packages.
535  *
536  * Revision 1.7  2005/05/11 15:21:53  cgruber
537  * Change enum to enumeration, since enum is now a keyword as of Java 5.0
538  *
539  * A few other comments in the code.
540  *
541  * Revision 1.6  2003/08/06 23:07:52  chochos
542  * general code cleanup (mostly, removing unused imports)
543  *
544  * Revision 1.5  2003/06/06 20:29:45  mpowers
545  * Now dequeuing the association when connection is broken.
546  * Coalesced change events had been processed even after breaking connection.
547  *
548  * Revision 1.4  2001/03/06 23:43:46  mpowers
549  * Implemented icon aspect for text association.
550  *
551  * Revision 1.3  2001/02/17 16:52:05  mpowers
552  * Changes in imports to support building with jdk1.1 collections.
553  *
554  * Revision 1.2  2001/01/25 17:46:11  mpowers
555  * Clarified usage and gc expectations.
556  *
557  * Revision 1.1.1.1  2000/12/21 15:48:07  mpowers
558  * Contributing wotonomy.
559  *
560  * Revision 1.11  2000/12/20 16:25:39  michael
561  * Added log to all files.
562  *
563  *
564  */
565