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.util.HashMap;
22 import java.util.Map;
23
24 import net.wotonomy.foundation.NSArray;
25 import net.wotonomy.foundation.NSMutableArray;
26 import net.wotonomy.foundation.NSNotificationCenter;
27 import net.wotonomy.foundation.internal.Introspector;
28 import net.wotonomy.foundation.internal.WotonomyException;
29
30 /***
31 * EOClassDescription provides meta-information about a class
32 * and is used to customize certain behaviors within wotonomy
33 * and specifically within editing contexts and object stores.
34 * <br><br>
35 *
36 * The default implementation works for most well-formed java beans,
37 * but you will want to create your own subclass most typically
38 * to customize the toOne and toMany relationships for your
39 * class to ensure that an entire graph of objects is not
40 * persisted in order to perist a single object.
41 * <br><br>
42 *
43 * The easiest way to register your subclass is to create it
44 * in the same package as the class it describes but with
45 * a "ClassDesc" suffix. For example, "my.package.MyEntity"
46 * would be described by "my.package.MyEntityClassDesc". <br><br>
47 *
48 * Note that while the interface is the same, the implementation
49 * of this class differs substantially from the specification
50 * in order to be more useful for java classes.
51 *
52 * @author michael@mpowers.net
53 * @author $Author: cgruber $
54 * @version $Revision: 900 $
55 */
56 public class EOClassDescription
57 {
58 /***
59 * A delete rule specifying that object(s) that reference
60 * this object should have those references set to null
61 * when this object is deleted.
62 */
63 public static final int DeleteRuleNullify = 0;
64
65 /***
66 * A delete rule specifying that object(s) referenced by
67 * this object should also be deleted when this object
68 * is deleted.
69 */
70 public static final int DeleteRuleCascade = 1;
71
72 /***
73 * A delete rule specicying that this object should
74 * not be allowed to be deleted if it references any
75 * object(s).
76 */
77 public static final int DeleteRuleDeny = 2;
78
79 /***
80 * A delete rule specifying that no action be taken
81 * when this object is deleted. This is the default.
82 */
83 public static final int DeleteRuleNoAction = 3;
84
85 /***
86 * Notification fired when a class description has been requested
87 * for a class. Observers should watch for this notification and
88 * call registerClassDescription so that class descriptions can be
89 * loaded on-demand.
90 * The notification's object is the requested class and the
91 * user info dictionary is null.
92 */
93 public static final String ClassDescriptionNeededForClassNotification =
94 "ClassDescriptionNeededForClassNotification";
95
96 /***
97 * Notification fired when a class description has been requested
98 * for an entity name. Observers should watch for this notification and
99 * call registerClassDescription so that class descriptions can be
100 * loaded on-demand.
101 * The notification's object is the requested name and the
102 * user info dictionary is null.
103 */
104 public static final String ClassDescriptionNeededForEntityNameNotification =
105 "ClassDescriptionNeededForEntityNameNotification";
106
107 public EOClassDescription() {
108 super();
109 }
110
111 /***
112 * Returns the class description that corresponds to the specified class.
113 * If the class description has not already been loaded, a
114 * ClassDescriptionNeededForClassNotification is posted.
115 * If the class description is still not found, the class' loader
116 * is consulted for a class in the same package and named the same as the
117 * specified class but appended with "ClassDesc", e.g. "EmployeeObjectClassDesc".
118 * If the class description is still not found, a class description is
119 * returned that uses java bean introspection to provide reasonable values.
120 */
121 public static EOClassDescription classDescriptionForClass(
122 Class aClass )
123 {
124 if ( classMap == null ) classMap = new HashMap();
125 EOClassDescription result = (EOClassDescription) classMap.get( aClass );
126 if ( result == null )
127 {
128
129 NSNotificationCenter.defaultCenter().postNotification(
130 ClassDescriptionNeededForClassNotification, aClass, null );
131 result = (EOClassDescription) classMap.get( aClass );
132 }
133 if ( result == null )
134 {
135
136 String className = aClass.getName() + ClassNameSuffix;
137 Class classDesc;
138 try
139 {
140 classDesc = aClass.getClassLoader().loadClass( className );
141 if ( classDesc != null )
142 {
143 result = (EOClassDescription) classDesc.newInstance();
144 registerClassDescription( result, aClass );
145 }
146 }
147 catch ( Exception exc )
148 {
149
150 }
151 }
152 if ( result == null )
153 {
154
155 result = new EOClassDescription( aClass );
156 registerClassDescription( result, aClass );
157 }
158 return result;
159 }
160
161 /***
162 * Returns the class description that corresponds to the specified
163 * entity name. If the class description has not already been
164 * loaded, a ClassDescriptionNeededForEntityNameNotification is posted.
165 * Returns null if no class description can be found for the entity name.
166 */
167 public static EOClassDescription classDescriptionForEntityName(
168 String aName )
169 {
170 if ( entityMap == null ) entityMap = new HashMap();
171 EOClassDescription result = (EOClassDescription) entityMap.get( aName );
172 if ( result == null )
173 {
174
175 NSNotificationCenter.defaultCenter().postNotification(
176 ClassDescriptionNeededForEntityNameNotification, aName, null );
177 result = (EOClassDescription) entityMap.get( aName );
178 }
179 return result;
180 }
181
182 /***
183 * Clears all cached class descriptions so that new requests
184 * for class descriptions will be re-loaded on-demand.
185 */
186 public static void invalidateClassDescriptionCache()
187 {
188 classMap.clear();
189 entityMap.clear();
190 }
191
192 /***
193 * Registers the specified class descriptiong for the specified class.
194 * Nulls are not allowed - to clear the cache call invalidateClassDescriptionCache().
195 */
196 public static void registerClassDescription(
197 EOClassDescription description,
198 Class aClass )
199 {
200 if ( classMap == null ) classMap = new HashMap();
201 if ( entityMap == null ) entityMap = new HashMap();
202 description.theClass = aClass;
203 classMap.put( aClass, description );
204 entityMap.put( description.entityName(), description );
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 /***
221 * The string appended to the java class name when
222 * searching the class path for an appropriate description.
223 */
224 private final static String ClassNameSuffix = "ClassDesc";
225
226 private static Map classMap;
227 private static Map entityMap;
228
229 protected Class theClass;
230 private NSMutableArray attributes;
231
232 /***
233 * Constructor may only be called by subclasses.
234 */
235 protected EOClassDescription( Class aClass )
236 {
237 theClass = aClass;
238 }
239
240 /***
241 * Returns a List of all the attributes for this class.
242 * This implementation reflects on the java class to produce
243 * a list of attributes, and then removes those keys that
244 * are returned by toOneRelationshipKeys and toManyRelationhipKeys.
245 */
246 public NSArray attributeKeys()
247 {
248 if ( attributes == null )
249 {
250 NSMutableArray readProperties = new NSMutableArray();
251 String[] read = Introspector.getReadPropertiesForClass( theClass );
252 for ( int i = 0; i < read.length; i++ )
253 {
254 readProperties.addObject( read[i] );
255 }
256
257 attributes = new NSMutableArray();
258 String[] write = Introspector.getWritePropertiesForClass( theClass );
259 for ( int i = 0; i < write.length; i++ )
260 {
261 attributes.addObject( write[i] );
262 }
263
264
265 attributes.retainAll( readProperties );
266
267
268 attributes.removeAll( toOneRelationshipKeys() );
269 attributes.removeAll( toManyRelationshipKeys() );
270 }
271 return attributes;
272 }
273
274 /***
275 * This method is called when the specified object has been
276 * fetched into the specified editing context. Fetch means
277 * an object was fetched using a fetch specification - it is
278 * not the same thing as an insertion.
279 * This implementation does nothing.
280 */
281 public void awakeObjectFromFetch(
282 Object object,
283 EOEditingContext anEditingContext )
284 {
285 }
286
287 /***
288 * This method is called when the specified object has been
289 * inserted into the specified editing context. Insertion
290 * means an object was inserted by a display group - it does
291 * not mean the same thing as a fetch.
292 * This implementation does nothing.
293 */
294 public void awakeObjectFromInsertion(
295 Object object,
296 EOEditingContext anEditingContext )
297 {
298
299 }
300
301 /***
302 * Returns the class decription for the object referenced
303 * by the specified relationship key, or null if the
304 * class description cannot be determined for that key.
305 * This implementation returns null.
306 */
307 public EOClassDescription classDescriptionForDestinationKey(
308 String detailKey )
309 {
310 return null;
311 }
312
313 /***
314 * Creates a new instance of the class represented by this
315 * class description, registering it with the specified
316 * editing context and global id. The class description
317 * may not keep references to the newly created object.
318 * The editing context and/or the id may be null.
319 * This implementation constructs a new instance of the class
320 * and registers it with the specified editing context.
321 * If the global id is specified, the object will be populated
322 * with the appropriate data, otherwise the object will be
323 * treated as a newly inserted object.
324 * If no editing context is specified, the global id is
325 * ignored and the new instance of the class is returned.
326 */
327 public Object createInstanceWithEditingContext(
328 EOEditingContext anEditingContext,
329 EOGlobalID globalID )
330 {
331
332 Object result = null;
333 try
334 {
335 result = theClass.newInstance();
336 if ( anEditingContext != null )
337 {
338 if ( globalID != null )
339 {
340 if ( result instanceof EOEnterpriseObject )
341 {
342 ((EOEnterpriseObject)result).awakeFromFetch( anEditingContext );
343 }
344
345 anEditingContext.recordObject( result, globalID );
346 }
347 else
348 {
349 if ( result instanceof EOEnterpriseObject )
350 {
351 ((EOEnterpriseObject)result).awakeFromInsertion( anEditingContext );
352 }
353
354 anEditingContext.insertObject( result );
355 }
356 }
357 }
358 catch ( Exception exc )
359 {
360
361 throw new WotonomyException( exc );
362 }
363 return result;
364 }
365
366
367
368
369
370
371
372
373
374 /***
375 * Returns the delete rule to be used for the specified
376 * relationship key.
377 * This implementation returns DeleteRuleNoAction.
378 */
379 public int deleteRuleForRelationshipKey(
380 String relationshipKey )
381 {
382 return DeleteRuleNoAction;
383 }
384
385 /***
386 * Returns a human-readable title for the specified key.
387 * For example, displayNameForKey( "firstName" ) might
388 * return "First Name".
389 * This implementation attempts to construct such a string
390 * from the key, uppercasing the first character and
391 * inserting spaces before subsequent uppercase characters.
392 */
393 public String displayNameForKey(
394 String key )
395 {
396 if ( key == null ) return "";
397 if ( key.length() == 0 ) return "";
398
399 StringBuffer result = new StringBuffer();
400 result.append( Character.toUpperCase( key.charAt(0) ) );
401
402 char c;
403 int len = key.length();
404 for ( int i = 1; i < len; i++ )
405 {
406 c = key.charAt(i);
407 if ( Character.isUpperCase( c ) )
408 {
409 result.append( ' ' );
410 }
411 result.append( c );
412 }
413
414 return result.toString();
415 }
416
417 /***
418 * Returns a human-readable title for the class of objects
419 * that this class description represents. For example,
420 * class CustomerObject might return "Customer".
421 * This implementation returns the class name.
422 */
423 public String entityName()
424 {
425 String result = theClass.getName();
426 int index = result.lastIndexOf( "." );
427 if ( index == -1 ) return result;
428 return result.substring( index+1 );
429 }
430
431 /***
432 * Returns the fetch specification associated with this
433 * class description that corresponds to the specified name,
434 * or null if not found.
435 * This implementation returns null.
436 */
437 public EOFetchSpecification fetchSpecificationNamed(
438 String aString )
439 {
440 return null;
441 }
442
443 /***
444 * Returns the relationship key by which the object at the
445 * other end of the specified relationship key refers to
446 * this object, or null if not found.
447 * This implementation returns null.
448 */
449 public String inverseForRelationshipKey(
450 String relationshipKey )
451 {
452 return null;
453 }
454
455 public boolean ownsDestinationObjectsForRelationshipKey(
456 String relationshipKey )
457 {
458 throw new WotonomyException( "Not implemented yet." );
459 }
460
461 /***
462 * Called when this object has been deleted from the
463 * specified editing context. The delete rules for this
464 * object's relationships should be executed.
465 */
466 public void propagateDeleteForObject(
467 Object object,
468 EOEditingContext anEditingContext )
469 {
470 throw new WotonomyException( "Not implemented yet." );
471 }
472
473 /***
474 * Returns a List of the "to many" relationships for
475 * this class.
476 * This implementation returns an empty list.
477 */
478 public NSArray toManyRelationshipKeys()
479 {
480 return NSArray.EmptyArray;
481 }
482
483 /***
484 * Returns a List of the "to one" relationships for
485 * this class.
486 * This implementation returns an empty list.
487 */
488 public NSArray toOneRelationshipKeys()
489 {
490 return NSArray.EmptyArray;
491 }
492
493 /***
494 * Returns a human-readable description of the specified object
495 * that should not exceed 60 characters.
496 * This implementation returns anObject.toString().
497 */
498 public String userPresentableDescriptionForObject(
499 Object anObject )
500 {
501 return anObject.toString();
502 }
503
504 /***
505 * Verifies that the specified object may be deleted.
506 * Throws an exception with a user-readable error message
507 * if the delete operation should not be allowed.
508 * This implementation does nothing.
509 */
510 public void validateObjectForDelete(
511 Object object )
512 {
513
514 }
515
516 /***
517 * Verifies that the specified object may be saved.
518 * Throws an exception with a user-readable error message
519 * if the save operation should not be allowed.
520 * This implementation does nothing.
521 */
522 public void validateObjectForSave(
523 Object object )
524 {
525
526 }
527
528 /***
529 * Validates the specified value for the specified key on this
530 * this class. Returns null if the value is acceptable, or
531 * returns an object that should be used in place of the specified
532 * object, or throws an exception with a user-readable error message
533 * if no acceptable value can be determined.
534 * This implementation returns null.
535 */
536 public Object validateValueForKey( Object value, String key)
537 {
538 return null;
539 }
540
541 /***
542 * Returns the Java Class that this description describes.
543 * NOTE: This method is not in the specification.
544 */
545 public Class getDescribedClass()
546 {
547 return theClass;
548 }
549
550 }
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601