1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.control;
20
21 import java.lang.reflect.Array;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.TreeSet;
30
31 import net.wotonomy.foundation.NSArray;
32 import net.wotonomy.foundation.NSMutableArray;
33 import net.wotonomy.foundation.internal.Introspector;
34 import net.wotonomy.foundation.internal.WotonomyException;
35
36 /***
37 * A data source that reads and writes to an indexed
38 * property of a java object. This class is used by
39 * MasterDetailAssociation to retreive objects from
40 * the master display group.
41 *
42 * @author michael@mpowers.net
43 * @author $Author: cgruber $
44 * @version $Revision: 894 $
45 */
46 public class PropertyDataSource extends OrderedDataSource
47 {
48 protected Object source;
49 protected String key;
50 protected Class lastKnownType;
51 protected EOClassDescription classDesc;
52 protected EOEditingContext context;
53
54 /***
55 * Creates a new PropertyDataSource with no editing context
56 * and will try to guess the appropriate class description
57 * when trying to create objects.
58 */
59 public PropertyDataSource()
60 {
61 this( null, (EOClassDescription) null );
62 }
63
64 /***
65 * Creates a new PropertyDataSource that uses the specified
66 * editing context, but will try to guess the appropriate
67 * class description when trying to create objects.
68 */
69 public PropertyDataSource( EOEditingContext aContext )
70 {
71 this( aContext, (EOClassDescription) null );
72 }
73
74 /***
75 * Creates a new PropertyDataSource that uses the specified
76 * editing context and vends objects of the specified class.
77 */
78 public PropertyDataSource(
79 EOEditingContext aContext, Class aClass )
80 {
81 this( aContext, EOClassDescription.classDescriptionForClass( aClass ) );
82 }
83
84 /***
85 * Creates a new PropertyDataSource that uses the specified
86 * editing context and vends objects of the specified
87 * class description.
88 */
89 public PropertyDataSource(
90 EOEditingContext aContext, EOClassDescription aClassDesc )
91 {
92 source = null;
93 key = null;
94 lastKnownType = null;
95 classDesc = aClassDesc;
96 context = aContext;
97 }
98
99 /***
100 * Provides the master object for detail display groups.
101 */
102 public Object source()
103 {
104 return source;
105 }
106
107 /***
108 * Allows a detail display group to set the master object.
109 */
110 public void setSource( Object anObject )
111 {
112 source = anObject;
113 }
114
115 /***
116 * Provides the detail key for detail display groups.
117 */
118 public String key()
119 {
120 return key;
121 }
122
123 /***
124 * Allows a detail display group to set the detail key.
125 */
126 public void setKey( String aKey )
127 {
128 key = aKey;
129 }
130
131 /***
132 * Inserts the specified object into this data source.
133 * Calls insertObjectAtIndex and appends to the end
134 * of the list.
135 */
136 public void insertObject ( Object anObject )
137 {
138 insertObjectAtIndex( anObject, -1 );
139 }
140
141 /***
142 * Inserts the specified object into this data source,
143 * at the specified index.
144 */
145 public void insertObjectAtIndex (
146 Object anObject, int anIndex )
147 {
148 if ( source == null ) return;
149 List list = readAsList();
150 if ( anIndex == -1 ) anIndex = list.size();
151 if ( anIndex > list.size() ) anIndex = list.size();
152 list.add( anIndex, anObject );
153 writeAsList( list );
154 }
155
156 /***
157 * Deletes the specified object from this data source.
158 */
159 public void deleteObject ( Object anObject )
160 {
161 if ( source == null ) return;
162 List list = readAsList();
163 list.remove( anObject );
164 writeAsList( list );
165 }
166
167 public EOEditingContext editingContext ()
168 {
169 return context;
170 }
171
172 /***
173 * Returns a List containing the objects in this
174 * data source.
175 */
176 public NSArray fetchObjects ()
177 {
178 if ( source == null ) return NSArray.EmptyArray;
179 return readAsList();
180 }
181
182 /***
183 * Returns a new instance of this class.
184 */
185 public EODataSource
186 dataSourceQualifiedByKey ( String aKey )
187 {
188
189 EOClassDescription keyClassDesc = null;
190 if ( classDesc != null )
191 {
192 keyClassDesc = classDesc.classDescriptionForDestinationKey( aKey );
193 }
194 return new PropertyDataSource( editingContext(), keyClassDesc );
195 }
196
197 /***
198 * Restricts this data source to vend those
199 * objects that are associated with the specified
200 * key on the specified object.
201 */
202 public void
203 qualifyWithRelationshipKey (
204 String aKey, Object anObject )
205 {
206 source = anObject;
207 key = aKey;
208 }
209
210 /***
211 * Returns the class description passed to the
212 * constructor, if any. If no class description and
213 * if the bound property is an indexed property,
214 * the type of the array is returned, otherwise
215 * this method returns null. This method is called
216 * by createObject().
217 */
218 public EOClassDescription
219 classDescriptionForObjects ()
220 {
221
222 if ( classDesc != null ) return classDesc;
223
224
225 EOClassDescription result = null;
226
227
228 Class type = lastKnownType;
229
230
231 if ( type == null )
232 {
233
234 if ( ( source != null ) && ( key != null ) )
235 {
236
237 Method m = Introspector.getPropertyReadMethod(
238 source.getClass(), key, new Class[0] );
239 if ( m != null )
240 {
241 Class returnType = m.getReturnType();
242 if ( returnType.isArray() )
243 {
244 type = returnType.getComponentType();
245 }
246 }
247 else
248 {
249 throw new WotonomyException( "Key does not exist for object: " + key + " : " + source );
250 }
251 }
252
253
254
255 }
256
257
258 if ( type != null )
259 {
260 result =
261 EOClassDescription.classDescriptionForClass( type );
262 }
263
264 return result;
265 }
266
267 /***
268 * Calls getValue() and returns the result as a List.
269 * Sets lastKnownType to the retrieved type.
270 */
271 protected NSMutableArray readAsList()
272 {
273 Object value = getValue();
274 if ( value == null )
275 {
276 return new NSMutableArray();
277 }
278
279 Object o;
280 NSMutableArray result = new NSMutableArray();
281 boolean hasReadType = false;
282 lastKnownType = null;
283
284
285 if ( value.getClass().isArray() )
286 {
287 int count = Array.getLength( value );
288 for ( int i = 0; i < count; i++ )
289 {
290 o = Array.get( value, i );
291 if ( o != null )
292 {
293
294 if ( hasReadType )
295 {
296
297 if ( o.getClass() != lastKnownType )
298 {
299
300 lastKnownType = null;
301 }
302 }
303 else
304 {
305
306 hasReadType = true;
307 lastKnownType = o.getClass();
308 }
309 }
310 result.add( o );
311 }
312 }
313 else
314 if ( value instanceof Collection )
315 {
316
317 Iterator i = ((Collection)value).iterator();
318 while ( i.hasNext() )
319 {
320 o = i.next();
321 if ( o != null )
322 {
323
324 if ( hasReadType )
325 {
326
327 if ( o.getClass() != lastKnownType )
328 {
329
330 lastKnownType = null;
331 }
332 }
333 else
334 {
335
336 hasReadType = true;
337 lastKnownType = o.getClass();
338 }
339 }
340 result.add( o );
341 }
342 }
343 else
344 {
345 lastKnownType = null;
346 throw new WotonomyException( "PropertyDataSource: " +
347 "bound property was not an indexed property: " + key );
348 }
349
350 return result;
351 }
352
353 /***
354 * Converts the specified List to lastKnownType
355 * and calls setValue().
356 */
357 protected void writeAsList( List anObjectList )
358 {
359 if ( source == null )
360 {
361 throw new WotonomyException( "PropertyDataSource: " +
362 "no source object: " + key );
363 }
364
365 Class c = source.getClass();
366 Method m = Introspector.getPropertyReadMethod( c, key, new Class[0] );
367 if ( m == null )
368 {
369 throw new WotonomyException( "Could not read property for object: "
370 + key + " : " + source + " (" + c + ")" );
371 }
372
373 Class returnType = m.getReturnType();
374
375 int count = anObjectList.size();
376 Object result = null;
377
378 if ( returnType.isArray() )
379 {
380 Class type = returnType.getComponentType();
381 result = Array.newInstance( type, count );
382 for ( int i = 0; i < count; i++ )
383 {
384 Array.set( result, i, anObjectList.get( i ) );
385 }
386 }
387 else
388 {
389 Collection collection = null;
390
391 if ( ! returnType.isInterface() )
392 {
393 try
394 {
395 collection = (Collection) returnType.newInstance();
396 }
397 catch ( Exception exc )
398 {
399
400 }
401 }
402
403
404 if ( collection == null )
405 {
406 if ( returnType.isAssignableFrom( NSMutableArray.class ) )
407 {
408 collection = new NSMutableArray();
409 }
410 else
411 if ( returnType.isAssignableFrom( LinkedList.class ) )
412 {
413 collection = new LinkedList();
414 }
415 else
416 if ( returnType.isAssignableFrom( ArrayList.class ) )
417 {
418 collection = new ArrayList();
419 }
420 else
421 if ( returnType.isAssignableFrom( HashSet.class ) )
422 {
423 collection = new HashSet();
424 }
425 else
426 if ( returnType.isAssignableFrom( TreeSet.class ) )
427 {
428 collection = new TreeSet();
429 }
430 }
431
432 if ( collection == null )
433 {
434 throw new WotonomyException( "Could not create a collection of type: " + returnType );
435 }
436
437 collection.addAll( anObjectList );
438 result = collection;
439 }
440
441 setValue( result );
442 }
443
444 /***
445 * Returns the value of the indexed property
446 * specified by qualifyWithRelationshipKey.
447 */
448 protected Object getValue()
449 {
450 if ( source instanceof EOKeyValueCoding )
451 {
452 return ((EOKeyValueCoding)source).valueForKey( key );
453 }
454 return EOKeyValueCodingSupport.valueForKey( source, key );
455 }
456
457 /***
458 * Sets the value of the indexed property
459 * specified by qualifyWithRelationshipKey.
460 * The argument is assumed to be of appropriate
461 * type for the property. EOObserverCenter is
462 * notified that the object will change.
463 */
464 protected void setValue( Object aValue )
465 {
466 EOClassDescription sourceDesc =
467 EOClassDescription.classDescriptionForClass( source.getClass() );
468
469
470
471
472 {
473
474 EOObserverCenter.notifyObserversObjectWillChange( source );
475 }
476
477
478 if ( source instanceof EOKeyValueCoding )
479 {
480 ((EOKeyValueCoding)source).takeValueForKey( aValue, key );
481 }
482 else
483 {
484 EOKeyValueCodingSupport.takeValueForKey( source, aValue, key );
485 }
486 }
487
488 }
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
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