Coverage Report - net.wotonomy.control.EOClassDescription
 
Classes in this File Line Coverage Branch Coverage Complexity
EOClassDescription
0% 
0% 
2.16
 
 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  0
                 super();
 109  0
         }
 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  0
         if ( classMap == null ) classMap = new HashMap();
 125  0
         EOClassDescription result = (EOClassDescription) classMap.get( aClass );
 126  0
         if ( result == null )
 127  
         {
 128  
             // if not found, post notification
 129  0
             NSNotificationCenter.defaultCenter().postNotification(
 130  0
                 ClassDescriptionNeededForClassNotification, aClass, null );
 131  0
             result = (EOClassDescription) classMap.get( aClass );
 132  
         }
 133  0
         if ( result == null )
 134  
         {
 135  
             // if not found, look for similarly named class
 136  0
             String className = aClass.getName() + ClassNameSuffix;
 137  
             Class classDesc;
 138  
             try
 139  
             {
 140  0
                 classDesc = aClass.getClassLoader().loadClass( className );
 141  0
                 if ( classDesc != null )
 142  
                 {
 143  0
                     result = (EOClassDescription) classDesc.newInstance();
 144  0
                     registerClassDescription( result, aClass );
 145  
                 }
 146  
             }
 147  0
             catch ( Exception exc )
 148  
             {
 149  
                 // ignore exceptions and resume   
 150  0
             }
 151  
         }            
 152  0
         if ( result == null )
 153  
         {
 154  
             // if not found, default to this class
 155  0
             result = new EOClassDescription( aClass );
 156  0
             registerClassDescription( result, aClass );
 157  
         }
 158  0
         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  0
         if ( entityMap == null ) entityMap = new HashMap();
 171  0
         EOClassDescription result = (EOClassDescription) entityMap.get( aName );
 172  0
         if ( result == null )
 173  
         {
 174  
             // if not found, post notification
 175  0
             NSNotificationCenter.defaultCenter().postNotification(
 176  0
                 ClassDescriptionNeededForEntityNameNotification, aName, null );
 177  0
             result = (EOClassDescription) entityMap.get( aName );
 178  
         }            
 179  0
         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  0
         classMap.clear();
 189  0
         entityMap.clear();
 190  0
     }
 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  0
         if ( classMap == null ) classMap = new HashMap();
 201  0
         if ( entityMap == null ) entityMap = new HashMap();
 202  0
         description.theClass = aClass;
 203  0
         classMap.put( aClass, description );
 204  0
         entityMap.put( description.entityName(), description );
 205  0
     }
 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  0
     protected EOClassDescription( Class aClass )
 236  0
     {
 237  0
         theClass = aClass;
 238  0
     }
 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  0
         if ( attributes == null )
 249  
         {
 250  0
             NSMutableArray readProperties = new NSMutableArray();
 251  0
             String[] read = Introspector.getReadPropertiesForClass( theClass );
 252  0
             for ( int i = 0; i < read.length; i++ )
 253  
             {
 254  0
                 readProperties.addObject( read[i] );
 255  
             }
 256  
     
 257  0
             attributes = new NSMutableArray();
 258  0
             String[] write = Introspector.getWritePropertiesForClass( theClass );
 259  0
             for ( int i = 0; i < write.length; i++ )
 260  
             {
 261  0
                 attributes.addObject( write[i] );
 262  
             }
 263  
             
 264  
             // only use properties on both lists: read/write
 265  0
             attributes.retainAll( readProperties );
 266  
             
 267  
             // remove relationship keys
 268  0
             attributes.removeAll( toOneRelationshipKeys() );
 269  0
             attributes.removeAll( toManyRelationshipKeys() );
 270  
         }
 271  0
         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  0
     }
 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  0
     }
 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  0
         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  0
         Object result = null;
 333  
         try
 334  
         {
 335  0
             result = theClass.newInstance();
 336  0
             if ( anEditingContext != null )
 337  
             {
 338  0
                 if ( globalID != null )
 339  
                 {
 340  0
                     if ( result instanceof EOEnterpriseObject )
 341  
                     {
 342  0
                         ((EOEnterpriseObject)result).awakeFromFetch( anEditingContext );
 343  
                     }
 344  
                     // register in editing context
 345  0
                     anEditingContext.recordObject( result, globalID );
 346  0
                 }
 347  
                 else // no global id specified
 348  
                 {
 349  0
                     if ( result instanceof EOEnterpriseObject )
 350  
                     {
 351  0
                         ((EOEnterpriseObject)result).awakeFromInsertion( anEditingContext );
 352  
                     }
 353  
                     // register as new object in editing context
 354  0
                     anEditingContext.insertObject( result );
 355  
                 }
 356  
             }
 357  
         }
 358  0
         catch ( Exception exc )
 359  
         {
 360  
             // error instantiating
 361  0
             throw new WotonomyException( exc );
 362  0
         }
 363  0
         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  0
         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  0
         if ( key == null ) return "";
 397  0
         if ( key.length() == 0 ) return "";
 398  
         
 399  0
         StringBuffer result = new StringBuffer();
 400  0
         result.append( Character.toUpperCase( key.charAt(0) ) );
 401  
         
 402  
         char c;
 403  0
         int len = key.length();
 404  0
         for ( int i = 1; i < len; i++ )
 405  
         {
 406  0
             c = key.charAt(i);
 407  0
             if ( Character.isUpperCase( c ) )
 408  
             {
 409  0
                 result.append( ' ' );
 410  
             }
 411  0
             result.append( c );
 412  
         }
 413  
         
 414  0
         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  0
             String result = theClass.getName();
 426  0
             int index = result.lastIndexOf( "." );
 427  0
             if ( index == -1 ) return result;
 428  0
             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  0
         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  0
         return null;
 453  
     }
 454  
 
 455  
     public boolean ownsDestinationObjectsForRelationshipKey(
 456  
         String relationshipKey )
 457  
     {
 458  0
         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  0
         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  0
         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  0
         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  0
         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  0
     }
 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  0
     }
 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  0
         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  0
         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