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