From aedc34d55462a75e329bbf342251ff6504cd117e Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Sun, 19 May 2024 17:56:33 -0400 Subject: Initial import from SVN --- .../net/wotonomy/control/EOClassDescription.java | 601 +++++++++++++++++++++ 1 file changed, 601 insertions(+) create mode 100644 projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java') diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java new file mode 100644 index 0000000..cd07ebb --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java @@ -0,0 +1,601 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.HashMap; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSNotificationCenter; +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOClassDescription provides meta-information about a class +* and is used to customize certain behaviors within wotonomy +* and specifically within editing contexts and object stores. +*

+* +* The default implementation works for most well-formed java beans, +* but you will want to create your own subclass most typically +* to customize the toOne and toMany relationships for your +* class to ensure that an entire graph of objects is not +* persisted in order to perist a single object. +*

+* +* The easiest way to register your subclass is to create it +* in the same package as the class it describes but with +* a "ClassDesc" suffix. For example, "my.package.MyEntity" +* would be described by "my.package.MyEntityClassDesc".

+* +* Note that while the interface is the same, the implementation +* of this class differs substantially from the specification +* in order to be more useful for java classes. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 900 $ +*/ +public class EOClassDescription +{ + /** + * A delete rule specifying that object(s) that reference + * this object should have those references set to null + * when this object is deleted. + */ + public static final int DeleteRuleNullify = 0; + + /** + * A delete rule specifying that object(s) referenced by + * this object should also be deleted when this object + * is deleted. + */ + public static final int DeleteRuleCascade = 1; + + /** + * A delete rule specicying that this object should + * not be allowed to be deleted if it references any + * object(s). + */ + public static final int DeleteRuleDeny = 2; + + /** + * A delete rule specifying that no action be taken + * when this object is deleted. This is the default. + */ + public static final int DeleteRuleNoAction = 3; + + /** + * Notification fired when a class description has been requested + * for a class. Observers should watch for this notification and + * call registerClassDescription so that class descriptions can be + * loaded on-demand. + * The notification's object is the requested class and the + * user info dictionary is null. + */ + public static final String ClassDescriptionNeededForClassNotification = + "ClassDescriptionNeededForClassNotification"; + + /** + * Notification fired when a class description has been requested + * for an entity name. Observers should watch for this notification and + * call registerClassDescription so that class descriptions can be + * loaded on-demand. + * The notification's object is the requested name and the + * user info dictionary is null. + */ + public static final String ClassDescriptionNeededForEntityNameNotification = + "ClassDescriptionNeededForEntityNameNotification"; + + public EOClassDescription() { + super(); + } + + /** + * Returns the class description that corresponds to the specified class. + * If the class description has not already been loaded, a + * ClassDescriptionNeededForClassNotification is posted. + * If the class description is still not found, the class' loader + * is consulted for a class in the same package and named the same as the + * specified class but appended with "ClassDesc", e.g. "EmployeeObjectClassDesc". + * If the class description is still not found, a class description is + * returned that uses java bean introspection to provide reasonable values. + */ + public static EOClassDescription classDescriptionForClass( + Class aClass ) + { + if ( classMap == null ) classMap = new HashMap(); + EOClassDescription result = (EOClassDescription) classMap.get( aClass ); + if ( result == null ) + { + // if not found, post notification + NSNotificationCenter.defaultCenter().postNotification( + ClassDescriptionNeededForClassNotification, aClass, null ); + result = (EOClassDescription) classMap.get( aClass ); + } + if ( result == null ) + { + // if not found, look for similarly named class + String className = aClass.getName() + ClassNameSuffix; + Class classDesc; + try + { + classDesc = aClass.getClassLoader().loadClass( className ); + if ( classDesc != null ) + { + result = (EOClassDescription) classDesc.newInstance(); + registerClassDescription( result, aClass ); + } + } + catch ( Exception exc ) + { + // ignore exceptions and resume + } + } + if ( result == null ) + { + // if not found, default to this class + result = new EOClassDescription( aClass ); + registerClassDescription( result, aClass ); + } + return result; + } + + /** + * Returns the class description that corresponds to the specified + * entity name. If the class description has not already been + * loaded, a ClassDescriptionNeededForEntityNameNotification is posted. + * Returns null if no class description can be found for the entity name. + */ + public static EOClassDescription classDescriptionForEntityName( + String aName ) + { + if ( entityMap == null ) entityMap = new HashMap(); + EOClassDescription result = (EOClassDescription) entityMap.get( aName ); + if ( result == null ) + { + // if not found, post notification + NSNotificationCenter.defaultCenter().postNotification( + ClassDescriptionNeededForEntityNameNotification, aName, null ); + result = (EOClassDescription) entityMap.get( aName ); + } + return result; + } + + /** + * Clears all cached class descriptions so that new requests + * for class descriptions will be re-loaded on-demand. + */ + public static void invalidateClassDescriptionCache() + { + classMap.clear(); + entityMap.clear(); + } + + /** + * Registers the specified class descriptiong for the specified class. + * Nulls are not allowed - to clear the cache call invalidateClassDescriptionCache(). + */ + public static void registerClassDescription( + EOClassDescription description, + Class aClass ) + { + if ( classMap == null ) classMap = new HashMap(); + if ( entityMap == null ) entityMap = new HashMap(); + description.theClass = aClass; + classMap.put( aClass, description ); + entityMap.put( description.entityName(), description ); + } + +/* + public static Object classDelegate() + { + throw new WotonomyException( "Not implemented yet." ); + } + + public static void setClassDelegate( + Object aDelegate) + { + throw new WotonomyException( "Not implemented yet." ); + } +*/ + + /** + * The string appended to the java class name when + * searching the class path for an appropriate description. + */ + private final static String ClassNameSuffix = "ClassDesc"; + + private static Map classMap; + private static Map entityMap; + + protected Class theClass; + private NSMutableArray attributes; + + /** + * Constructor may only be called by subclasses. + */ + protected EOClassDescription( Class aClass ) + { + theClass = aClass; + } + + /** + * Returns a List of all the attributes for this class. + * This implementation reflects on the java class to produce + * a list of attributes, and then removes those keys that + * are returned by toOneRelationshipKeys and toManyRelationhipKeys. + */ + public NSArray attributeKeys() + { + if ( attributes == null ) + { + NSMutableArray readProperties = new NSMutableArray(); + String[] read = Introspector.getReadPropertiesForClass( theClass ); + for ( int i = 0; i < read.length; i++ ) + { + readProperties.addObject( read[i] ); + } + + attributes = new NSMutableArray(); + String[] write = Introspector.getWritePropertiesForClass( theClass ); + for ( int i = 0; i < write.length; i++ ) + { + attributes.addObject( write[i] ); + } + + // only use properties on both lists: read/write + attributes.retainAll( readProperties ); + + // remove relationship keys + attributes.removeAll( toOneRelationshipKeys() ); + attributes.removeAll( toManyRelationshipKeys() ); + } + return attributes; + } + + /** + * This method is called when the specified object has been + * fetched into the specified editing context. Fetch means + * an object was fetched using a fetch specification - it is + * not the same thing as an insertion. + * This implementation does nothing. + */ + public void awakeObjectFromFetch( + Object object, + EOEditingContext anEditingContext ) + { + } + + /** + * This method is called when the specified object has been + * inserted into the specified editing context. Insertion + * means an object was inserted by a display group - it does + * not mean the same thing as a fetch. + * This implementation does nothing. + */ + public void awakeObjectFromInsertion( + Object object, + EOEditingContext anEditingContext ) + { + // does nothing + } + + /** + * Returns the class decription for the object referenced + * by the specified relationship key, or null if the + * class description cannot be determined for that key. + * This implementation returns null. + */ + public EOClassDescription classDescriptionForDestinationKey( + String detailKey ) + { + return null; + } + + /** + * Creates a new instance of the class represented by this + * class description, registering it with the specified + * editing context and global id. The class description + * may not keep references to the newly created object. + * The editing context and/or the id may be null. + * This implementation constructs a new instance of the class + * and registers it with the specified editing context. + * If the global id is specified, the object will be populated + * with the appropriate data, otherwise the object will be + * treated as a newly inserted object. + * If no editing context is specified, the global id is + * ignored and the new instance of the class is returned. + */ + public Object createInstanceWithEditingContext( + EOEditingContext anEditingContext, + EOGlobalID globalID ) + { +//System.out.println( "createInstanceWithEditingContext: " + this + " : " + theClass ); + Object result = null; + try + { + result = theClass.newInstance(); + if ( anEditingContext != null ) + { + if ( globalID != null ) + { + if ( result instanceof EOEnterpriseObject ) + { + ((EOEnterpriseObject)result).awakeFromFetch( anEditingContext ); + } + // register in editing context + anEditingContext.recordObject( result, globalID ); + } + else // no global id specified + { + if ( result instanceof EOEnterpriseObject ) + { + ((EOEnterpriseObject)result).awakeFromInsertion( anEditingContext ); + } + // register as new object in editing context + anEditingContext.insertObject( result ); + } + } + } + catch ( Exception exc ) + { + // error instantiating + throw new WotonomyException( exc ); + } + return result; + } + +/* + public NSFormatter defaultFormatterForKey( + String key ) + { + throw new WotonomyException( "Not implemented yet." ); + } +*/ + + /** + * Returns the delete rule to be used for the specified + * relationship key. + * This implementation returns DeleteRuleNoAction. + */ + public int deleteRuleForRelationshipKey( + String relationshipKey ) + { + return DeleteRuleNoAction; + } + + /** + * Returns a human-readable title for the specified key. + * For example, displayNameForKey( "firstName" ) might + * return "First Name". + * This implementation attempts to construct such a string + * from the key, uppercasing the first character and + * inserting spaces before subsequent uppercase characters. + */ + public String displayNameForKey( + String key ) + { + if ( key == null ) return ""; + if ( key.length() == 0 ) return ""; + + StringBuffer result = new StringBuffer(); + result.append( Character.toUpperCase( key.charAt(0) ) ); + + char c; + int len = key.length(); + for ( int i = 1; i < len; i++ ) + { + c = key.charAt(i); + if ( Character.isUpperCase( c ) ) + { + result.append( ' ' ); + } + result.append( c ); + } + + return result.toString(); + } + + /** + * Returns a human-readable title for the class of objects + * that this class description represents. For example, + * class CustomerObject might return "Customer". + * This implementation returns the class name. + */ + public String entityName() + { + String result = theClass.getName(); + int index = result.lastIndexOf( "." ); + if ( index == -1 ) return result; + return result.substring( index+1 ); + } + + /** + * Returns the fetch specification associated with this + * class description that corresponds to the specified name, + * or null if not found. + * This implementation returns null. + */ + public EOFetchSpecification fetchSpecificationNamed( + String aString ) + { + return null; + } + + /** + * Returns the relationship key by which the object at the + * other end of the specified relationship key refers to + * this object, or null if not found. + * This implementation returns null. + */ + public String inverseForRelationshipKey( + String relationshipKey ) + { + return null; + } + + public boolean ownsDestinationObjectsForRelationshipKey( + String relationshipKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Called when this object has been deleted from the + * specified editing context. The delete rules for this + * object's relationships should be executed. + */ + public void propagateDeleteForObject( + Object object, + EOEditingContext anEditingContext ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a List of the "to many" relationships for + * this class. + * This implementation returns an empty list. + */ + public NSArray toManyRelationshipKeys() + { + return NSArray.EmptyArray; + } + + /** + * Returns a List of the "to one" relationships for + * this class. + * This implementation returns an empty list. + */ + public NSArray toOneRelationshipKeys() + { + return NSArray.EmptyArray; + } + + /** + * Returns a human-readable description of the specified object + * that should not exceed 60 characters. + * This implementation returns anObject.toString(). + */ + public String userPresentableDescriptionForObject( + Object anObject ) + { + return anObject.toString(); + } + + /** + * Verifies that the specified object may be deleted. + * Throws an exception with a user-readable error message + * if the delete operation should not be allowed. + * This implementation does nothing. + */ + public void validateObjectForDelete( + Object object ) + { + // does nothing + } + + /** + * Verifies that the specified object may be saved. + * Throws an exception with a user-readable error message + * if the save operation should not be allowed. + * This implementation does nothing. + */ + public void validateObjectForSave( + Object object ) + { + // does nothing + } + + /** + * Validates the specified value for the specified key on this + * this class. Returns null if the value is acceptable, or + * returns an object that should be used in place of the specified + * object, or throws an exception with a user-readable error message + * if no acceptable value can be determined. + * This implementation returns null. + */ + public Object validateValueForKey( Object value, String key) + { + return null; + } + + /** + * Returns the Java Class that this description describes. + * NOTE: This method is not in the specification. + */ + public Class getDescribedClass() + { + return theClass; + } + +} + +/* + * $Log$ + * Revision 1.3 2006/02/18 22:46:44 cgruber + * Add Surrogate map from .util into control's internal package, and fix imports. + * + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.11 2003/08/08 05:50:32 chochos + * theClass is protected instead of private + * + * Revision 1.10 2003/08/08 00:37:44 chochos + * default constructor is needed by subclasses + * + * Revision 1.9 2001/12/20 18:55:46 mpowers + * Hooks for awakeFromInsertion and awakeFromFetch. + * + * Revision 1.8 2001/12/01 23:51:45 mpowers + * Corrected createWithEditingContext. + * + * Revision 1.7 2001/11/25 22:43:38 mpowers + * Corrected createInstanceWithEditingContext. + * + * Revision 1.6 2001/04/29 02:29:31 mpowers + * Debugging relationship faulting. + * + * Revision 1.5 2001/04/28 22:17:51 mpowers + * Revised PropertyDataSource to be EOClassDescription-aware. + * + * Revision 1.4 2001/04/28 14:12:23 mpowers + * Refactored cloning/copying into KeyValueCodingUtilities. + * + * Revision 1.3 2001/04/27 23:37:20 mpowers + * Now using EOClassDescription in the EODataSource class, as we should. + * + * Revision 1.2 2001/04/27 00:27:42 mpowers + * Partial implementation. + * + * Revision 1.1 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * + */ + + -- cgit v1.2.3