View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2001 Intersect Software Corporation
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.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             // if not found, post notification
129             NSNotificationCenter.defaultCenter().postNotification(
130                 ClassDescriptionNeededForClassNotification, aClass, null );
131             result = (EOClassDescription) classMap.get( aClass );
132         }
133         if ( result == null )
134         {
135             // if not found, look for similarly named class
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                 // ignore exceptions and resume   
150             }
151         }            
152         if ( result == null )
153         {
154             // if not found, default to this class
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             // if not found, post notification
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     public static Object classDelegate()
209     {
210         throw new WotonomyException( "Not implemented yet." );
211     }
212 
213     public static void setClassDelegate(
214         Object aDelegate)
215     {
216         throw new WotonomyException( "Not implemented yet." );
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             // only use properties on both lists: read/write
265             attributes.retainAll( readProperties );
266             
267             // remove relationship keys
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         // does nothing
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 //System.out.println( "createInstanceWithEditingContext: " + this + " : " + theClass );
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                     // register in editing context
345                     anEditingContext.recordObject( result, globalID );
346                 }
347                 else // no global id specified
348                 {
349                     if ( result instanceof EOEnterpriseObject )
350                     {
351                         ((EOEnterpriseObject)result).awakeFromInsertion( anEditingContext );
352                     }
353                     // register as new object in editing context
354                     anEditingContext.insertObject( result );
355                 }
356             }
357         }
358         catch ( Exception exc )
359         {
360             // error instantiating
361             throw new WotonomyException( exc );
362         }
363         return result;
364     }
365   
366 /*
367     public NSFormatter defaultFormatterForKey(
368         String key )
369     {
370         throw new WotonomyException( "Not implemented yet." );
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         // does nothing
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         // does nothing
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  * $Log$
554  * Revision 1.3  2006/02/18 22:46:44  cgruber
555  * Add Surrogate map from .util into control's internal package, and fix imports.
556  *
557  * Revision 1.2  2006/02/16 16:47:14  cgruber
558  * Move some classes in to "internal" packages and re-work imports, etc.
559  *
560  * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
561  *
562  * Revision 1.1  2006/02/16 13:19:57  cgruber
563  * Check in all sources in eclipse-friendly maven-enabled packages.
564  *
565  * Revision 1.11  2003/08/08 05:50:32  chochos
566  * theClass is protected instead of private
567  *
568  * Revision 1.10  2003/08/08 00:37:44  chochos
569  * default constructor is needed by subclasses
570  *
571  * Revision 1.9  2001/12/20 18:55:46  mpowers
572  * Hooks for awakeFromInsertion and awakeFromFetch.
573  *
574  * Revision 1.8  2001/12/01 23:51:45  mpowers
575  * Corrected createWithEditingContext.
576  *
577  * Revision 1.7  2001/11/25 22:43:38  mpowers
578  * Corrected createInstanceWithEditingContext.
579  *
580  * Revision 1.6  2001/04/29 02:29:31  mpowers
581  * Debugging relationship faulting.
582  *
583  * Revision 1.5  2001/04/28 22:17:51  mpowers
584  * Revised PropertyDataSource to be EOClassDescription-aware.
585  *
586  * Revision 1.4  2001/04/28 14:12:23  mpowers
587  * Refactored cloning/copying into KeyValueCodingUtilities.
588  *
589  * Revision 1.3  2001/04/27 23:37:20  mpowers
590  * Now using EOClassDescription in the EODataSource class, as we should.
591  *
592  * Revision 1.2  2001/04/27 00:27:42  mpowers
593  * Partial implementation.
594  *
595  * Revision 1.1  2001/03/29 03:29:49  mpowers
596  * Now using KeyValueCoding and Support instead of Introspector.
597  *
598  *
599  */
600     
601