1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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";
74 public static final String IsLeafAspect = "isLeaf";
75 public static final String EditableAspect = "editable";
76 public static final String VisibleAspect = "visible";
77 public static final String ObjectsAspect = "objects";
78 public static final String LabelAspect = "label";
79 public static final String IconAspect = "icon";
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
187 EODisplayGroup oldGroup = displayGroupForAspect( anAspect );
188 if ( oldGroup != null )
189 {
190 EOObserverCenter.removeObserver( this, oldGroup );
191 }
192
193
194 if ( aDisplayGroup != null )
195 {
196 aspectToGroup.setObjectForKey( aDisplayGroup, anAspect );
197 }
198 else
199 {
200 aspectToGroup.removeObjectForKey( anAspect );
201 }
202
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
250
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 ()
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
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
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(),
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
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
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
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
523 return group.valueForObjectAtIndex( anIndex, key );
524 }
525
526 }
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565