summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control
diff options
context:
space:
mode:
authorBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
committerBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
commitaedc34d55462a75e329bbf342251ff6504cd117e (patch)
treebcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control
Initial import from SVN
Diffstat (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control')
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java762
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java219
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java187
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java167
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java601
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java82
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java673
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java164
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java339
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java48
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java152
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java323
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java3247
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java219
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java102
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java79
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java565
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java151
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java83
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java72
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java151
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java146
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java106
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java41
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java129
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java176
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java235
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java233
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java165
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java129
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java113
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java320
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java204
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java547
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java104
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java51
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java169
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java680
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java42
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java76
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java406
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java219
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java72
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java79
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java283
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java740
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java346
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java57
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java549
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java259
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html6
51 files changed, 15068 insertions, 0 deletions
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java
new file mode 100644
index 0000000..ddaacf5
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java
@@ -0,0 +1,762 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationCenter;
+import net.wotonomy.foundation.NSNotificationQueue;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* An abstract implementation of object store that
+* implements common functionality. Subclasses must
+* implement data object creation, initialization, and
+* refault logic, as well as logic to commit an editing
+* context.
+*/
+public abstract class AbstractObjectStore extends EOObjectStore
+{
+ private NSMutableArray insertedIDsBuffer;
+ private NSMutableArray updatedIDsBuffer;
+ private NSMutableArray deletedIDsBuffer;
+ private NSMutableArray invalidatedIDsBuffer;
+
+ private Map snapshots;
+ private List exceptionList;
+
+ /**
+ * Constructs a new instance of this object store.
+ */
+ public AbstractObjectStore()
+ {
+ snapshots = new HashMap();
+ exceptionList = null;
+
+ insertedIDsBuffer = new NSMutableArray();
+ updatedIDsBuffer = new NSMutableArray();
+ deletedIDsBuffer = new NSMutableArray();
+ invalidatedIDsBuffer = new NSMutableArray();
+
+ // register for notifications
+ NSSelector handleNotification =
+ new NSSelector( "handleNotification",
+ new Class[] { NSNotification.class } );
+ NSNotificationCenter.defaultCenter().addObserver(
+ this,
+ handleNotification,
+ EOClassDescription.ClassDescriptionNeededForEntityNameNotification,
+ null );
+ }
+
+ /**
+ * This implementation returns an appropriately configured array fault.
+ */
+ public NSArray arrayFaultWithSourceGlobalID( EOGlobalID aGlobalID,
+ String aRelationship, EOEditingContext aContext )
+ { // System.out.println( "arrayFaultWithSourceGlobalID: " + aGlobalID + " : " + aRelationship );
+ return new ArrayFault(
+ aGlobalID, aRelationship, aContext );
+ }
+
+ /**
+ * This implementation returns the actual object for the specified id.
+ */
+ public /*EOEnterpriseObject*/Object faultForGlobalID( EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ { // System.out.println( "faultForGlobalID: " + aGlobalID );
+ return /*(EOEnterpriseObject)*/createInstanceWithEditingContext( aGlobalID, aContext );
+ }
+
+ /**
+ * Returns a fault representing an object of the specified entity type with
+ * values from the specified dictionary. The fault should belong to the
+ * specified editing context.
+ * NOTE: Faults are not supported yet.
+ */
+ public /*EOEnterpriseObject*/Object faultForRawRow( Map aDictionary, String anEntityName,
+ EOEditingContext aContext )
+ {
+ //TODO: raw rows are not yet supported
+ throw new WotonomyException( "Faults are not yet supported." );
+ }
+
+ /**
+ * Given a newly instantiated object, this method initializes its
+ * properties to values appropriate for the specified id. The object
+ * should belong to the specified editing context. This method is called
+ * to populate faults.
+ */
+ public void initializeObject( Object anObject, EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ { //System.out.println( "initializeObject: " + aGlobalID );
+ try
+ {
+ String entity = entityForGlobalIDOrObject( aGlobalID, null );
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForEntityName( entity );
+ if ( classDesc == null )
+ {
+ throw new WotonomyException( "Unknown entity type: " + entity );
+ }
+
+ Collection attributes = classDesc.attributeKeys();
+ Map data = readFromCache( aGlobalID, attributes );
+ String key;
+ Iterator iterator = attributes.iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next().toString();
+
+ // write the snapshot's reference into the object
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)anObject).takeStoredValueForKey( data.get( key ), key );
+ }
+ else
+ {
+ EOKeyValueCodingSupport.takeStoredValueForKey( anObject, data.get( key ), key );
+ }
+
+ //NOTE: our objects are expected to make a copy
+ // of their data before it is modified, so it's okay
+ // to return them our copy of the data:
+ // we trust that they will not modify it.
+ }
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ }
+ }
+
+ /**
+ * Reads the local data snapshot for the specified id.
+ * If no snapshot exists, a new snapshot is created.
+ * If the specified keys are not in the snapshot,
+ * new data is fetched into the snapshot.
+ * If null is specified, all known keys are returned.
+ * Will not return null.
+ * Result will have values for those keys and only
+ * those keys requested. Missing keys indicate an
+ * error occurred.
+ */
+ protected Map readFromCache( EOGlobalID aGlobalID, Collection keys )
+ {
+ Map snapshot = (Map) snapshots.get( aGlobalID );
+
+ // if no snapshot for this id, create an empty one
+ if ( snapshot == null )
+ {
+ snapshot = new HashMap();
+ snapshots.put( aGlobalID, snapshot );
+ }
+
+ // if we don't have all the necessary keys
+ if ( ( keys == null ) || ( ! snapshot.keySet().containsAll( keys ) ) )
+ {
+ // we need to make a server call
+ try
+ {
+ Map data = readObject( aGlobalID, keys );
+
+ // compare timestamps
+ Comparable localTimestamp = (Comparable) timestampForData( snapshot );
+ // if our local snapshot has an timestamp (new snapshots don't have timestamp)
+ if ( localTimestamp != null )
+ {
+ Comparable incomingTimestamp = (Comparable) timestampForData( data );
+ if ( incomingTimestamp == null )
+ {
+ // not allowed to happen
+ new RuntimeException( "Server returned data without an timestamp" ).printStackTrace();
+ // however, we can just assume it's a newer timestamp and continue
+ }
+
+ // if timestamps don't match
+ if ( ( incomingTimestamp == null ) || ( ! incomingTimestamp.equals( localTimestamp ) ) )
+ {
+ // dump our existing snapshot's data
+ snapshot.clear();
+ // queue for a notification on this oid as updated
+ //TODO: implement this
+ }
+ }
+
+ // copy new data into our local snapshot
+ snapshot.putAll( data );
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ }
+ }
+
+ // return just the requested keys from our updated snapshot
+ Map result = new HashMap();
+ if ( keys == null )
+ {
+ result.putAll( snapshot );
+ }
+ else
+ {
+ Object key;
+ Iterator iterator = keys.iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next();
+ result.put( key, snapshot.get( key ) );
+ }
+ }
+ return snapshot;
+ }
+
+ /**
+ * Returns a comparable object (typically a Date or Long) for
+ * the given data map or snapshot. This is used to determine
+ * whether a local snapshot should be dumped in favor of fetched
+ * data from the server.
+ * Returns null if no timestamp can be determined, in which
+ * case the fetched data will assumed to be more recent than
+ * any local snapshot.
+ */
+ abstract protected Comparable timestampForData( Map aDataMap );
+
+ /**
+ * Extracts the global id for the fetched data or snapshot.
+ * Some entities have multi-attribute keys that would be
+ * assembled into a single instance of EOGlobalID.
+ */
+ abstract protected EOGlobalID globalIDForData( Map aDataMap );
+
+ /**
+ * Returns the entity that corresponds to the specified global id
+ * and/or object. Either may be null, but both will not be null.
+ * //FIXME: This is less than elegant.
+ */
+ abstract protected String entityForGlobalIDOrObject(
+ EOGlobalID aGlobalID, Object anObject );
+
+ /**
+ * Returns the keys that have changed on the specified object.
+ * If null, all keys are presumed changed, including relationships.
+ */
+ abstract protected Collection changedKeysForObject( Object anObject );
+
+ /**
+ * Returns the data for the row corresponding to the specified id
+ * containing at least the specified keys. Implementations are allowed
+ * to return more data than requested, and callers are advised to take
+ * advantage of the returned data.
+ */
+ abstract protected Map readObject( EOGlobalID aGlobalID, Collection keys );
+
+ /**
+ * Returns the data for the row corresponding to the specified id.
+ * //TODO: Need a better return value? How to return invalidated list?
+ */
+ abstract protected Map insertObject( EOGlobalID aGlobalID, Map aDataMap );
+
+ /**
+ * Returns the data for the row corresponding to the specified id.
+ * //TODO: Need a better return value? How to return invalidated list?
+ */
+ abstract protected Object updateObject( EOGlobalID aGlobalID, Map aDataMap );
+
+ /**
+ * Returns the data for the row corresponding to the specified id.
+ * //TODO: Need a better return value? How to return invalidated list?
+ */
+ abstract protected Object deleteObject( EOGlobalID aGlobalID );
+
+ /**
+ * Creates a new instance of an object that corresponds to the
+ * specified global id and is registered in the specified context.
+ * This implementation extracts the entity type from getEntityForGlobaID
+ * and construct a new instance from the class description that
+ * corresponds to the entity type. Override to change this behavior.
+ */
+ protected Object createInstanceWithEditingContext(
+ EOGlobalID aGlobalID, EOEditingContext aContext )
+ {
+ String entity = entityForGlobalIDOrObject( aGlobalID, null );
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForEntityName( entity );
+ if ( classDesc == null )
+ {
+ throw new WotonomyException( "Unknown entity type: " + entity );
+ }
+
+ Object result = classDesc.createInstanceWithEditingContext( aContext, aGlobalID );
+ if ( result instanceof EOFaulting )
+ {
+ ((EOFaulting)result).turnIntoFault( null );
+ }
+ return result;
+ }
+
+ /**
+ * Dumps the snapshot corresponding to the specified id.
+ */
+ protected void invalidateObject( EOGlobalID aGlobalID )
+ {
+ snapshots.remove( aGlobalID );
+ }
+
+ /**
+ * Dumps all snapshots.
+ */
+ protected void invalidateAllCache()
+ {
+ snapshots.clear();
+ }
+
+ /**
+ * Remove all values from all objects in memory, turning them into faults,
+ * and posts a notification that all objects have been invalidated.
+ */
+ public void invalidateAllObjects()
+ {
+ invalidateAllCache();
+
+ // post notification
+ NSNotificationQueue.defaultQueue().enqueueNotification(
+ new NSNotification(
+ InvalidatedAllObjectsInStoreNotification, this ),
+ NSNotificationQueue.PostNow );
+ }
+
+ /**
+ * Removes values with the specified ids from memory, turning them into
+ * faults, and posts a notification that those objects have been invalidated.
+ */
+ public void invalidateObjectsWithGlobalIDs( List aList )
+ {
+ NSArray empty = new NSArray();
+ NSMutableArray invalidated = new NSMutableArray();
+
+ Object object;
+ Iterator iterator = aList.iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ invalidateObject( (EOGlobalID) object );
+ invalidated.addObject( object );
+ }
+
+ NSMutableDictionary info = new NSMutableDictionary();
+ info.setObjectForKey( empty, InsertedKey );
+ info.setObjectForKey( empty, UpdatedKey );
+ info.setObjectForKey( empty, DeletedKey );
+ info.setObjectForKey( invalidated, InvalidatedKey );
+
+ // post notification
+ NSNotificationQueue.defaultQueue().
+ enqueueNotificationWithCoalesceMaskForModes( new NSNotification(
+ ObjectsChangedInStoreNotification, this, info ),
+ NSNotificationQueue.PostNow,
+ NSNotificationQueue.NotificationNoCoalescing, null );
+ }
+
+ /**
+ * Returns false because locking is not currently permitted.
+ */
+ public boolean isObjectLockedWithGlobalID( EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ return false;
+ }
+
+ /**
+ * Does nothing because locking is not currently permitted.
+ */
+ public void lockObjectWithGlobalID( EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ // does nothing
+ }
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship. This method may not return an array fault
+ * because array faults call this method to fetch on demand.
+ * All objects must be registered the specified editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public NSArray objectsForSourceGlobalID( EOGlobalID aGlobalID,
+ String aRelationship, EOEditingContext aContext )
+ { // System.out.println( "objectsForSourceGlobalID: " + aGlobalID + " : " + aRelationship + " : " );
+
+ Map snapshot = readFromCache( aGlobalID, new NSArray( aRelationship ) );
+ Object value = snapshot.get( aRelationship );
+ if ( value == null ) value = new NSArray(); // empty list
+ if ( ! ( value instanceof Collection ) )
+ {
+ throw new RuntimeException( "Specified relationship is not a collection: "
+ + aRelationship + " : " + aGlobalID + " : " + value );
+ }
+
+ NSArray result = new NSMutableArray();
+
+ // get fault for each id
+ EOGlobalID id;
+ Object fault;
+ Iterator iterator = ((Collection)value).iterator();
+ while ( iterator.hasNext() )
+ {
+ id = (EOGlobalID) iterator.next();
+
+ // get registered fault
+ fault = aContext.faultForGlobalID( id, aContext );
+
+ // assert fault
+ if ( fault == null )
+ {
+ // this should never happen
+ throw new RuntimeException(
+ "Could not find fault for ID: " + id );
+ }
+
+ result.add( fault );
+ }
+
+ fireObjectsChangedInStore();
+
+//System.out.println( "done" );
+ return result;
+ }
+
+ /**
+ * Returns a List of objects the meet the criteria of
+ * the supplied specification. Faults are not allowed in the array.
+ * Each object is registered with the specified editing context.
+ * If any object is already fetched in the specified context,
+ * it is not refetched and that object should be used in the array.
+ */
+ public NSArray objectsWithFetchSpecification(
+ EOFetchSpecification aFetchSpec, EOEditingContext aContext )
+ {
+ NSMutableArray result = new NSMutableArray();
+
+ //TODO: implement this
+
+ return result;
+ }
+
+ /**
+ * Fires ObjectsChangedInStoreNotification
+ * with contents of buffers and then clears buffers.
+ * If buffers are empty, does nothing.
+ */
+ private void fireObjectsChangedInStore()
+ {
+ // check for changes to broadcast
+ if ( insertedIDsBuffer.size() + updatedIDsBuffer.size() +
+ deletedIDsBuffer.size() + invalidatedIDsBuffer.size() == 0 )
+ {
+ return;
+ }
+
+ // broadcast ObjectsChangedInStoreNotification
+ // for the benefit of child editing contexts
+
+ NSMutableDictionary storeInfo = new NSMutableDictionary();
+
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) insertedIDsBuffer ),
+ EOObjectStore.InsertedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) updatedIDsBuffer ),
+ EOObjectStore.UpdatedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) deletedIDsBuffer ),
+ EOObjectStore.DeletedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) invalidatedIDsBuffer ),
+ EOObjectStore.InvalidatedKey );
+
+ // clear buffers
+
+ insertedIDsBuffer.removeAllObjects();
+ updatedIDsBuffer.removeAllObjects();
+ deletedIDsBuffer.removeAllObjects();
+ invalidatedIDsBuffer.removeAllObjects();
+
+ // post notification
+ NSNotificationQueue.defaultQueue().
+ enqueueNotificationWithCoalesceMaskForModes( new NSNotification(
+ ObjectsChangedInStoreNotification, this, storeInfo ),
+ NSNotificationQueue.PostNow,
+ NSNotificationQueue.NotificationNoCoalescing, null );
+ }
+
+ /**
+ * Removes all values from the specified object,
+ * converting it into a fault for the specified id.
+ * New or deleted objects should not be refaulted.
+ */
+ public void refaultObject( Object anObject, EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+//System.out.println( "refaultObject: " + aGlobalID );
+//new net.wotonomy.ui.swing.util.StackTraceInspector();
+ if ( anObject instanceof EOFaulting )
+ {
+ ((EOFaulting)anObject).turnIntoFault( null );
+ }
+ }
+
+ /**
+ * Writes all changes in the specified editing context
+ * to the respository.
+ */
+ public void saveChangesInEditingContext ( EOEditingContext aContext )
+ {
+ Object result; // need a container result?
+ Map updateMap;
+ Object object;
+ EOGlobalID id;
+ Iterator iterator;
+
+ //TODO: the ordering of operations here
+ // needs to be a lot more sophisticated.
+
+ // process deletes first
+ iterator = aContext.deletedObjects().iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ id = aContext.globalIDForObject( object );
+ try
+ {
+ result = deleteObject( id );
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error deleting object: " + id );
+ exc.printStackTrace();
+ }
+ }
+
+ // process inserts next
+ iterator = aContext.insertedObjects().iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ processInsert( aContext, object );
+ }
+
+ // process updates last
+ iterator = aContext.updatedObjects().iterator();
+ while ( iterator.hasNext() )
+ {
+ object = iterator.next();
+ id = aContext.globalIDForObject( object );
+ try
+ {
+ updateMap = getUpdateMap( aContext, object );
+ result = updateObject( id, updateMap );
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error updating object: " + id );
+ exc.printStackTrace();
+ }
+ }
+
+ //aContext.invalidateAllObjects();
+ }
+
+ protected Object processInsert( EOEditingContext aContext, Object object )
+ {
+ Map result = null;
+ EOGlobalID id = aContext.globalIDForObject( object );
+ try
+ {
+ Map updateMap;
+ updateMap = getUpdateMap( aContext, object );
+ result = insertObject( id, updateMap );
+ id = globalIDForData( result ); // read new permanent id
+
+ // broadcast that the global id has changed.
+ NSMutableDictionary userInfo = new NSMutableDictionary();
+ userInfo.setObjectForKey( id, aContext.globalIDForObject( object ) );
+ NSNotificationQueue.defaultQueue().enqueueNotification(
+ new NSNotification( EOGlobalID.GlobalIDChangedNotification,
+ null , userInfo ), NSNotificationQueue.PostNow );
+
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error inserting object: " + id );
+ exc.printStackTrace();
+ }
+ return result;
+ }
+
+ /**
+ * This method returns a map containing just the keys that are modified
+ * for a given object, converting any to-one or to-many relationships
+ * to id references.
+ */
+ protected Map getUpdateMap( EOEditingContext aContext, Object anObject )
+ {
+ Map result = new HashMap();
+ EOEditingContext context = aContext;
+
+ String entity = entityForGlobalIDOrObject( null, anObject );
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForEntityName( entity );
+ if ( classDesc == null )
+ {
+ throw new WotonomyException( "Unknown entity type: " + entity );
+ }
+
+ NSArray oneKeys = classDesc.toOneRelationshipKeys();
+ NSArray manyKeys = classDesc.toManyRelationshipKeys();
+
+ String key;
+ Object value;
+ EOGlobalID id;
+
+ Collection changedKeys = changedKeysForObject( anObject );
+ if ( changedKeys == null )
+ {
+ // assume all keys changed
+ changedKeys = classDesc.attributeKeys();
+ changedKeys.addAll( oneKeys );
+ changedKeys.addAll( manyKeys );
+ }
+ Iterator iterator = changedKeys.iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next().toString();
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ value = ((EOKeyValueCoding)anObject).storedValueForKey( key );
+ }
+ else
+ {
+ value = EOKeyValueCodingSupport.storedValueForKey( anObject, key );
+ }
+
+ // convert to-one relationship to oid
+ if ( oneKeys.contains( key ) )
+ {
+ id = context.globalIDForObject( value );
+
+ // if this id hasn't been persisted, save it first
+ // NOTE: this won't work for self-referential graphs of objects!
+ if ( id.isTemporary() )
+ {
+ processInsert( aContext, value );
+ id = context.globalIDForObject( value );
+ }
+
+ value = id;
+ }
+ else
+ // convert to-many relationship list to oid list
+ if ( manyKeys.contains( key ) )
+ {
+ //NOTE: we can assume that array faults that
+ // are marked as changed have been fired.
+ if ( value instanceof Collection )
+ {
+ Object object;
+ Collection newValue = new LinkedList();
+ Iterator jiterator = ((Collection)value).iterator();
+ while ( jiterator.hasNext() )
+ {
+ object = jiterator.next();
+ id = context.globalIDForObject( object );
+
+ // if this id hasn't been persisted, save it first
+ // NOTE: this won't work for self-referential graphs of objects!
+ if ( id.isTemporary() )
+ {
+ processInsert( aContext, object );
+ id = context.globalIDForObject( object );
+ }
+ newValue.add( id );
+ }
+ value = newValue;
+ }
+ else
+ {
+ // should never happen
+ new RuntimeException(
+ "Can't update to-many relationship because it's not a Collection." )
+ .printStackTrace();
+ }
+ }
+
+ // place value in map
+ result.put( key, value );
+ }
+
+System.out.println( result );
+ return result;
+ }
+
+/*
+ * $Log$
+ * 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.5 2003/12/18 15:37:38 mpowers
+ * Changes to retain ability to work with objects that don't necessarily
+ * implement EOEnterpriseObject. I would still like to preserve this case
+ * for general usage, however the access package is free to assume that
+ * those objects will be EOs and cast appropriately.
+ *
+ * Revision 1.4 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.3 2002/10/24 18:18:12 mpowers
+ * NSArray's are now considered read-only, so we can return our internal
+ * representation to reduce unnecessary object allocation.
+ *
+ * Revision 1.2 2002/01/19 17:27:49 mpowers
+ * Implemented most of it.
+ *
+ * Revision 1.1 2001/11/25 22:44:02 mpowers
+ * Contributing draft of AbstractObjectStore.
+ *
+ *
+ */
+}
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java
new file mode 100644
index 0000000..59e135b
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java
@@ -0,0 +1,219 @@
+/*
+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.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import net.wotonomy.foundation.NSArray;
+
+/**
+* A class that extends NSArray to intercept any accessor calls
+* in order to defer loading until the last possible moment.<br><br>
+*
+* Because ArrayFault inherits from NSArray which implements
+* List which implements Collection, data objects may declare
+* their relationships to be of type NSArray, List, or Collection.<br><br>
+*
+* This class should be returned by implementations of
+* EOObjectStore.arrayFaultForSourceGlobalID().
+*/
+public class ArrayFault extends NSArray
+{
+ private EOEditingContext editingContext;
+ private EOGlobalID sourceID;
+ private String relationshipKey;
+ private boolean fetched;
+
+ public ArrayFault(
+ EOGlobalID aSourceID,
+ String aRelationshipKey,
+ EOEditingContext aContext )
+ {
+ super();
+ editingContext = aContext;
+ sourceID = aSourceID;
+ relationshipKey = aRelationshipKey;
+ fetched = false;
+ }
+
+ public boolean isFetched()
+ {
+ return fetched;
+ }
+
+ protected void fireFault()
+ {
+ if ( !fetched )
+ {
+//new net.wotonomy.ui.swing.util.StackTraceInspector();
+//System.out.println( "ArrayFault.fireFault: before:" + this );
+ fetched = true;
+ super.protectedAddAll(
+ editingContext.parentObjectStore().objectsForSourceGlobalID(
+ sourceID,
+ relationshipKey,
+ editingContext ) );
+//System.out.println( "ArrayFault.fireFault: after:" + this );
+ }
+ }
+
+ public Object clone()
+ {
+ fireFault();
+ return super.clone();
+ }
+
+ public boolean contains(Object elem)
+ {
+ fireFault();
+ return super.contains( elem );
+ }
+
+ public boolean equals(Object o)
+ {
+ fireFault();
+ return super.equals( o );
+ }
+
+ public Object get(int index)
+ {
+ fireFault();
+ return super.get( index );
+ }
+
+ /**
+ * Overridden to return the identity hash.
+ * This somewhat violates the List contract,
+ * but otherwise calling hash code would
+ * fire the fault. Bottom line: don't use
+ * array faults as keys in hash maps.
+ */
+ public int hashCode()
+ {
+ return System.identityHashCode( this );
+ }
+
+ public int indexOf(Object o)
+ {
+ fireFault();
+ return super.indexOf( o );
+ }
+
+ public boolean isEmpty()
+ {
+ fireFault();
+ return super.isEmpty();
+ }
+
+ public Iterator iterator()
+ {
+ fireFault();
+ return super.iterator();
+ }
+
+ public int lastIndexOf(Object o)
+ {
+ fireFault();
+ return super.lastIndexOf( o );
+ }
+
+ public ListIterator listIterator()
+ {
+ fireFault();
+ return super.listIterator();
+ }
+
+ public ListIterator listIterator(int index)
+ {
+ fireFault();
+ return super.listIterator( index );
+ }
+
+ public int size()
+ {
+ fireFault();
+ return super.size();
+ }
+
+ public List subList(int fromIndex, int toIndex)
+ {
+ fireFault();
+ return super.subList( fromIndex, toIndex );
+ }
+
+ public Object[] toArray()
+ {
+ fireFault();
+ return super.toArray();
+ }
+
+ public Object[] toArray(Object[] a)
+ {
+ fireFault();
+ return super.toArray( a );
+ }
+
+ /**
+ * Overridden to display information about
+ * the fault only if not fetched.
+ * Calls to super if fetched.
+ */
+ public String toString()
+ {
+ if ( isFetched() )
+ {
+ return super.toString();
+ }
+ return "[ArrayFault@"+Integer.toHexString( System.identityHashCode( this ) )+":"+sourceID+":"+relationshipKey+"]";
+ }
+}
+
+/*
+ * $Log$
+ * 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.5 2003/12/18 15:37:38 mpowers
+ * Changes to retain ability to work with objects that don't necessarily
+ * implement EOEnterpriseObject. I would still like to preserve this case
+ * for general usage, however the access package is free to assume that
+ * those objects will be EOs and cast appropriately.
+ *
+ * Revision 1.4 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.3 2002/10/24 18:17:37 mpowers
+ * ArrayFaults are now read-only.
+ *
+ * Revision 1.2 2001/05/06 22:22:55 mpowers
+ * Debugging.
+ *
+ * Revision 1.1 2001/05/05 23:05:42 mpowers
+ * Implemented Array Faults.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java
new file mode 100644
index 0000000..8123668
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java
@@ -0,0 +1,187 @@
+package net.wotonomy.control;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+
+/**
+* A data source that automates the process of
+* creating a child editing context and copying
+* objects from a parent context into it.
+* Attach this data source to a display group
+* that represents a "detail" or "drill-down"
+* view. <br><br>
+*
+* Once created, editingContext() will return the
+* child context, and fetch() will return the objects
+* that were copied into the child context.
+*/
+public class ChildDataSource extends EODataSource
+{
+ private EODataSource parent;
+ private EOEditingContext context;
+ private EOClassDescription classDescription;
+ private NSMutableArray objects;
+
+ /**
+ * Creates a child editing context for the
+ * specified parent's context and copies the
+ * specified object into the child context.
+ * The object must exist in the parent context.
+ * fetch() will return the child's object.
+ */
+ public ChildDataSource(
+ EODataSource aParentSource,
+ Object anObject )
+ {
+ this( aParentSource, new NSArray( (Object) anObject ) );
+ }
+
+ /**
+ * Creates a child editing context for the
+ * specified parent's context and copies the
+ * specified objects into the child context.
+ * The objects must exist in the parent context.
+ * The order of the parent's objects in the
+ * collection will determine the order in
+ * which the child objects are returned from
+ * fetch().
+ */
+ public ChildDataSource(
+ EODataSource aParentSource,
+ Collection anObjectList )
+ {
+ EOEditingContext parentContext =
+ aParentSource.editingContext();
+
+ parent = aParentSource;
+ context = new EOEditingContext( parentContext );
+//!new net.wotonomy.ui.swing.util.ObjectInspector( context );
+ objects = new NSMutableArray();
+ classDescription = null;
+
+ Object o;
+ Object copy;
+ boolean allSameClass = true;
+ Iterator it = anObjectList.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+
+ // determine class
+ if ( allSameClass == true )
+ {
+ Class c = o.getClass();
+ if ( classDescription == null )
+ {
+ classDescription =
+ EOClassDescription.classDescriptionForClass( c );
+ }
+ else
+ {
+ if ( c != classDescription.getDescribedClass() )
+ {
+ allSameClass = false;
+ classDescription = null;
+ }
+ }
+ }
+
+ // copy and add to list
+ objects.addObject( parentContext.faultForGlobalID(
+ parentContext.globalIDForObject( o ), context ) );
+ }
+ }
+
+ /**
+ * Returns the editing context for this data source,
+ * which was created in the constructor and whose
+ * parent is the editing context specified in the
+ * constructor.
+ */
+ public EOEditingContext editingContext()
+ {
+ return context;
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ public void insertObject ( Object anObject )
+ {
+
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ public void deleteObject ( Object anObject )
+ {
+
+ }
+
+ /**
+ * Returns a List containing the objects in this
+ * data source. This implementation returns all
+ * TestObjects that have been persisted to the
+ * datastore in the data directory.
+ */
+ public NSArray fetchObjects ()
+ {
+ return new NSArray( (Collection) objects );
+ }
+
+ /**
+ * Returns a data source that is capable of
+ * manipulating objects of the type returned by
+ * applying the specified key to objects
+ * vended by this data source.
+ * This implementation forwards the call to
+ * the parent data source.
+ * @see #qualifyWithRelationshipKey
+ */
+ public EODataSource
+ dataSourceQualifiedByKey ( String aKey )
+ {
+ //FIXME: This is fundamentally broken.
+ // Objects vended from the returned source
+ // are not registered in our editing context.
+ // We probably need yet another utility data
+ // source class that would wrap another source
+ // and convert vended objects into a different
+ // context.
+
+ return parent.dataSourceQualifiedByKey( aKey );
+ }
+
+ /**
+ * Restricts this data source to vend those
+ * objects that are associated with the specified
+ * key on the specified object.
+ * This implementation forwards the call to
+ * the parent data source.
+ */
+ public void
+ qualifyWithRelationshipKey (
+ String aKey, Object anObject )
+ {
+ parent.qualifyWithRelationshipKey( aKey, anObject );
+ }
+
+ /**
+ * Returns the description of the class of the
+ * objects that is vended by this data source,
+ * or null if this cannot be determined.
+ * This implementation returns the class of the
+ * objects passed to the constructor if they are
+ * all the same class, otherwise returns null.
+ */
+ public EOClassDescription
+ classDescriptionForObjects ()
+ {
+ return classDescription;
+ }
+
+}
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java
new file mode 100644
index 0000000..049eb33
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java
@@ -0,0 +1,167 @@
+/*
+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.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOAndQualifier contains other EOQualifiers,
+* evaluating as true only if all of the contained
+* qualifiers evaluate as true.
+*
+* @author michael@mpowers.net
+* @author yjcheung@intersectsoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOAndQualifier extends EOQualifier
+ implements EOKeyValueArchiving, EOQualifierEvaluation
+{
+ private List qualifiers;
+
+ public EOAndQualifier(
+ List aQualifierList )
+ {
+ qualifiers = new LinkedList( aQualifierList );
+ }
+
+ /**
+ * Returns a List of qualifiers contained by this qualifier.
+ */
+ public NSArray qualifiers()
+ {
+ return new NSArray( qualifiers );
+ }
+
+ /**
+ * Add a new qualifier to the list.
+ */
+ public void addQualifier(EOQualifier qualifier)
+ {
+ qualifiers.add(qualifier);
+ }
+
+ /**
+ * Evaluates this qualifier for the specified object,
+ * and returns whether the object is qualified.
+ * selector() is invoked on the value for key() on the
+ * specified object, with value() as the parameter.
+ *
+ * Note: this has the lazy "and" implementation. Ex.
+ * Qal1 and Qal2. If the Qal1 is evaluated to be false, then it returns
+ * false without evaluating Qa12.
+ */
+ public boolean evaluateWithObject( Object anObject )
+ {
+ boolean retVal = true;
+ Iterator it = qualifiers.iterator();
+ while (it.hasNext() && retVal)
+ {
+ retVal = ((EOQualifier) it.next()).evaluateWithObject(anObject);
+ }
+ return retVal;
+ }
+
+ /**
+ * Returns a string representation of this qualifier.
+ */
+ public String toString()
+ {
+ StringBuffer myBuf = new StringBuffer("(");
+ Iterator it = qualifiers.iterator();
+ while (it.hasNext())
+ {
+ myBuf = myBuf.append(((EOQualifier) it.next()).toString()).append(" and ");
+ }
+ String myStr = myBuf.toString();
+ myStr = myStr.substring(0, myStr.lastIndexOf(" and")).concat(")");
+ return myStr;
+ }
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) {
+ NSArray a = (NSArray)arch.decodeObjectForKey("qualifiers");
+ if (a == null)
+ return null;
+ NSMutableArray l = new NSMutableArray();
+ for (int i = 0; i < a.count(); i++) {
+ NSDictionary d = (NSDictionary)a.objectAtIndex(i);
+ EOKeyValueUnarchiver ua = new EOKeyValueUnarchiver(d);
+ EOQualifier q = (EOQualifier)EOQualifier.decodeWithKeyValueUnarchiver(ua);
+ if (q != null)
+ l.addObject(q);
+ }
+ return new EOAndQualifier(l);
+ }
+
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EOAndQualifier", "class");
+ NSMutableArray arr = new NSMutableArray(qualifiers.size());
+ for (int i = 0; i < qualifiers.size(); i++) {
+ EOQualifier q = (EOQualifier)qualifiers.get(i);
+ if (q instanceof EOKeyValueArchiving) {
+ EOKeyValueArchiver ar2 = new EOKeyValueArchiver();
+ ((EOKeyValueArchiving)q).encodeWithKeyValueArchiver(ar2);
+ arr.addObject(ar2.dictionary());
+ } else
+ throw new WotonomyException("Cannot archive instance of " + q.getClass().getName());
+ }
+ arch.encodeObject(arr, "qualifiers");
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.6 2003/08/12 01:43:04 chochos
+ * formally implement EOQualifierEvaluation
+ *
+ * Revision 1.5 2003/08/09 01:22:51 chochos
+ * qualifiers implement EOKeyValueArchiving
+ *
+ * Revision 1.4 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.3 2001/10/31 15:25:14 mpowers
+ * Cleanup of qualifiers.
+ *
+ * Revision 1.2 2001/10/30 22:57:28 mpowers
+ * EOQualifier framework is now working.
+ *
+ * Revision 1.1 2001/09/13 15:25:56 mpowers
+ * Started implementation of the EOQualifier framework.
+ *
+ *
+ */
+
+
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.
+* <br><br>
+*
+* 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.
+* <br><br>
+*
+* 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". <br><br>
+*
+* 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.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java
new file mode 100644
index 0000000..072f867
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java
@@ -0,0 +1,82 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2002 Israfil consulting Services 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
+
+$Id: EOCooperatingObjectStore.java 894 2006-02-16 16:47:14Z cgruber $
+
+*/
+
+package net.wotonomy.control;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSLocking;
+/**
+* A representation of a channel of communication to the database.
+*
+* @author cgruber@israfil.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+
+public abstract class EOCooperatingObjectStore extends EOObjectStore
+ implements NSLocking {
+
+ public EOCooperatingObjectStore() {
+ }
+
+ public abstract boolean ownsGlobalID(EOGlobalID eoglobalid);
+
+ public abstract boolean ownsObject(EOEnterpriseObject eoenterpriseobject);
+
+ public abstract boolean handlesFetchSpecification(EOFetchSpecification eofetchspecification);
+
+ public abstract void prepareForSaveWithCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator, EOEditingContext eoeditingcontext);
+
+ public abstract void recordChangesInEditingContext();
+
+ public abstract void recordUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary);
+
+ public abstract void performChanges();
+
+ public abstract void commitChanges();
+
+ public abstract void rollbackChanges();
+
+ public abstract NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject);
+
+ public abstract void lock();
+
+ public abstract void unlock();
+
+}
+/*
+ * $Log$
+ * 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.1 2002/07/14 21:59:06 mpowers
+ * Contributions from cgruber.
+ *
+ * Revision 1.2 2002/06/21 22:14:30 cgruber
+ * Add a log trail
+ *
+ */
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java
new file mode 100644
index 0000000..6b262cb
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java
@@ -0,0 +1,673 @@
+/*
+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.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSSet;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOCustomObject implements all the necessary interfaces to
+* receive first-class treatment from the control framework.
+* The implementation delegates as much class meta-behavior as
+* possible to EOClassDescription, letting subclasses
+* focus exclusively on business logic while still allowing
+* them to customize as much class behavior as needed.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOCustomObject
+ implements EOEnterpriseObject,
+ EOKeyValueCodingAdditions,
+ EODeferredFaulting,
+ EORelationshipManipulation,
+ EOValidation
+{
+ private transient static EOClassDescription classDescription;
+ private transient EOEditingContext editingContext;
+
+ // static configuration
+
+ /**
+ * Specifies whether the implementation of EOKeyValueCoding
+ * is permitted to access field directly. This implementation
+ * returns true; subclasses may override to customize this behavior.
+ */
+ public static boolean canAccessFieldsDirectly()
+ {
+ return true;
+ }
+
+ /**
+ * Specifies whether the implementation of EOKeyValueCoding
+ * is permitted to access private accessors. This implementation
+ * returns true; subclasses may override to customize this behavior.
+ */
+ public static boolean shouldUseStoredAccessors()
+ {
+ return true;
+ }
+
+ /**
+ * Specifies whether deferred faults should be used. This implementation
+ * returns false; subclasses may override to customize this behavior.
+ */
+ public static boolean usesDeferredFaultCreation()
+ {
+ return false;
+ }
+
+ // constructors
+
+ /**
+ * Default constructor initializes private state.
+ * EditingContext and ClassDescription are set to null.
+ */
+ public EOCustomObject()
+ {
+ editingContext = null;
+ classDescription = null;
+ }
+
+ /**
+ * Preferred constructor, specifying an editing context,
+ * a class description, and a global id, any or all of which
+ * may be null. Subclasses should invoke this constructor.
+ */
+ public EOCustomObject(
+ EOEditingContext aContext,
+ EOClassDescription aClassDescription,
+ EOGlobalID aGlobalID )
+ {
+ editingContext = aContext;
+ classDescription = aClassDescription;
+ }
+
+ // interface EOEnterpriseObject
+
+ /**
+ * Returns a List of all property keys defined on this object.
+ * This includes both attributes and relationships.
+ * This implementation returns the union of attributeKeys,
+ * toOneRelationshipKeys, and toManyRelationshipKeys.
+ */
+ public NSArray allPropertyKeys()
+ {
+ NSSet union = new NSSet();
+ union.addAll( attributeKeys() );
+ union.addAll( toOneRelationshipKeys() );
+ union.addAll( toManyRelationshipKeys() );
+ return new NSArray( (Collection) union );
+ }
+
+ /**
+ * Returns a list of all attributes defined on this object.
+ * Attributes are all properties that are not relationships.
+ * This implementation retrieves the keys from the class
+ * description.
+ */
+ public NSArray attributeKeys()
+ {
+ return classDescription().attributeKeys();
+ }
+
+ //void awakeFromClientUpdate(EOEditingContext aContext)
+
+ /**
+ * Called when the object has first been fetched into the
+ * specified editing context. This implementation calls
+ * awakeObjectFromFetch on the class description.
+ */
+ public void awakeFromFetch(EOEditingContext anEditingContext)
+ {
+ classDescription().awakeObjectFromFetch( this, anEditingContext );
+ }
+
+ /**
+ * Called when the object has been inserted into the
+ * specified editing context. This implementation calls
+ * awakeObjectFromInsertion on the class description.
+ */
+ public void awakeFromInsertion(EOEditingContext anEditingContext)
+ {
+ classDescription().awakeObjectFromInsertion( this, anEditingContext );
+ }
+
+ /**
+ * Returns a Map representing the delta of the current state
+ * from the state represented in the specified snapshot.
+ * The result will contain only the keys that have changed
+ * and their values. Relationship keys will map to an NSArray
+ * that contains an NSArray of added objects and an NSArray
+ * of removed objects, in that order.
+ */
+ public NSDictionary changesFromSnapshot(NSDictionary snapshot)
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a class description for this object.
+ * Calls EOClassDescription.classDescriptionForClass.
+ */
+ public EOClassDescription classDescription()
+ {
+ if ( classDescription == null )
+ {
+ classDescription = EOClassDescription.classDescriptionForClass( getClass() );
+ if ( classDescription == null )
+ {
+ throw new WotonomyException(
+ "No class description found for class: " + getClass() );
+ }
+ }
+ return classDescription;
+ }
+
+ /**
+ * Returns a class description for the object at the
+ * other end of the specified relationship key.
+ * This implementation calls to the classDescription.
+ */
+ public EOClassDescription classDescriptionForDestinationKey(String aKey)
+ {
+ return classDescription().classDescriptionForDestinationKey( aKey );
+ }
+
+ /**
+ * Clears all property values for this object.
+ * This method is called to clean-up an object that
+ * will no longer be used, and implementations should
+ * ensure that all references are set to null to
+ * prevent problems with garbage-collection.
+ */
+ public void clearProperties()
+ {
+ //FIXME: clear properties here
+ }
+
+ /**
+ * Returns the delete rule constant defined on EOClassDescription
+ * for the relationship defined by the specified key.
+ * This implementation calls to the classDescription.
+ */
+ public int deleteRuleForRelationshipKey(String aRelationshipKey)
+ {
+ return classDescription().deleteRuleForRelationshipKey( aRelationshipKey );
+ }
+
+ /**
+ * Returns the editing context in which this object is registered.
+ */
+ public EOEditingContext editingContext()
+ {
+ return editingContext;
+ }
+
+ /**
+ * Returns the name of the entity that this object represents.
+ */
+ public String entityName()
+ {
+ return classDescription().entityName();
+ }
+
+ /**
+ * Returns a String containing all property keys and values for
+ * this object. Relationships should be represented by calling
+ * eoShallowDescription() on the object.
+ */
+ public String eoDescription()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a String containing all attribute keys and values for
+ * this object. Relationships are not included.
+ */
+ public String eoShallowDescription()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns the key used to reference this object on the
+ * object at the other end of the specified relationship.
+ * This implementation calls to the class description.
+ */
+ public String inverseForRelationshipKey(String aRelationshipKey)
+ {
+ return classDescription().inverseForRelationshipKey( aRelationshipKey );
+ }
+
+ //Object invokeRemoteMethod(
+ // String aMethodName, Class[] aTypeArray Object[] anArgumentArray)
+
+ /**
+ * Returns whether the specified relationship key represents
+ * a to-many relationship.
+ */
+ public boolean isToManyKey(String aKey)
+ {
+ return toManyRelationshipKeys().containsObject( aKey );
+ }
+
+ /**
+ * Returns whether the objects at the other end of the specified
+ * relationship should be deleted when this object is deleted.
+ * This implementation calls to the class description.
+ */
+ public boolean ownsDestinationObjectsForRelationshipKey(String aKey)
+ {
+ return classDescription().ownsDestinationObjectsForRelationshipKey( aKey );
+ }
+
+ //void prepareValuesForClient()
+
+ /**
+ * Called to perform the delete propagation for this object
+ * on the specified editing context. All relationships
+ * should be processed according to their corresponding
+ * delete rule.
+ * This implementation calls to the class description.
+ */
+ public void propagateDeleteWithEditingContext(EOEditingContext aContext)
+ {
+ classDescription().propagateDeleteForObject( this, aContext );
+ }
+
+ /**
+ * Applies the changes from the specified snapshot to
+ * this object.
+ * @see #changesFromSnapshot(NSDictionary)
+ */
+ public void reapplyChangesFromDictionary(NSDictionary aDeltaSnapshot)
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a snapshot of the current state of this object.
+ * All property keys are mapped to their values; nulls are
+ * represented by NSNull.
+ */
+ public NSDictionary snapshot()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a List of the to-many relationship keys
+ * for this object.
+ * This implementation calls to the class description.
+ */
+ public NSArray toManyRelationshipKeys()
+ {
+ return classDescription().toManyRelationshipKeys();
+ }
+
+ /**
+ * Returns a List of the to-one relationship keys
+ * for this object.
+ * This implementation calls to the class description.
+ */
+ public NSArray toOneRelationshipKeys()
+ {
+ return classDescription().toOneRelationshipKeys();
+ }
+
+ /**
+ * Applies the specified snapshot to this object,
+ * converting NSNulls to null and calling
+ * takeStoredValueForKey for each key in the Map.
+ */
+ public void updateFromSnapshot(NSDictionary aSnapshot)
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a short, stateful string representation
+ * of this object.
+ * This implementation calls to the class description.
+ */
+ public String userPresentableDescription()
+ {
+ return classDescription().userPresentableDescriptionForObject( this );
+ }
+
+ /**
+ * This method should be called by each setter method
+ * on this object before changes are made to the
+ * object's internal state. This implementation calls
+ * EOObserverCenter.notifyObserversObjectWillChange( this ),
+ */
+ public void willChange()
+ {
+ EOObserverCenter.notifyObserversObjectWillChange( this );
+ }
+
+ // interface EOKeyValueCoding
+
+ /**
+ * Returns the value for the specified property.
+ * If the property does not exist, this method should
+ * call handleQueryWithUnboundKey.
+ */
+ public Object valueForKey( String aKey )
+ {
+ return EOKeyValueCodingSupport.valueForKey( this, aKey );
+ }
+
+ /**
+ * Sets the property to the specified value.
+ * If the property does not exist, this method should
+ * call handleTakeValueForUnboundKey.
+ * If the property is of a type that cannot allow
+ * null (e.g. primitive types) and aValue is null,
+ * this method should call unableToSetNullForKey.
+ */
+ public void takeValueForKey( Object aValue, String aKey )
+ {
+ EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey );
+ }
+
+ /**
+ * Returns the value for the private field that
+ * corresponds to the specified property.
+ */
+ public Object storedValueForKey( String aKey )
+ {
+ return EOKeyValueCodingSupport.storedValueForKey( this, aKey );
+ }
+
+ /**
+ * Sets the the private field that corresponds to the
+ * specified property to the specified value.
+ */
+ public void takeStoredValueForKey( Object aValue, String aKey )
+ {
+ EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey );
+ }
+
+ /**
+ * Called by valueForKey when the specified key is
+ * not found on this object. Implementing classes
+ * should handle the specified value or otherwise
+ * throw an exception.
+ */
+ public Object handleQueryWithUnboundKey( String aKey )
+ {
+ return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey );
+ }
+
+ /**
+ * Called by takeValueForKey when the specified key
+ * is not found on this object. Implementing classes
+ * should handle the specified value or otherwise
+ * throw an exception.
+ */
+ public void handleTakeValueForUnboundKey( Object aValue, String aKey )
+ {
+ EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey );
+ }
+
+ /**
+ * Called by takeValueForKey when the type of the
+ * specified key is not allowed to be null, as is
+ * the case with primitive types. Implementing
+ * classes should handle this case appropriately
+ * or otherwise throw an exception.
+ */
+ public void unableToSetNullForKey( String aKey )
+ {
+ EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey );
+ }
+
+ // interface EOKeyValueCodingAdditions
+
+ /**
+ * Returns the value for the specified key path, which is
+ * a series of keys delimited by ".", for example:
+ * "createTime.year.length".
+ */
+ public Object valueForKeyPath( String aKeyPath )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the value for the specified key path, which is
+ * a series of keys delimited by ".", for example:
+ * "createTime.year.length".
+ * The value is set for the last object referenced by
+ * the key path.
+ */
+ public void takeValueForKeyPath( Object aValue, String aKeyPath )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map of the specified keys to their values,
+ * each of which might be obtained by calling valueForKey.
+ */
+ public NSDictionary valuesForKeys( List aKeyList )
+ {
+ return KeyValueCodingUtilities.valuesForKeys( this, aKeyList );
+ }
+
+ /**
+ * Takes the keys from the specified map as properties
+ * and applies the corresponding values, each of which
+ * might be set by calling takeValueForKey.
+ */
+ public void takeValuesFromDictionary( Map aMap )
+ {
+ KeyValueCodingUtilities.takeValuesFromDictionary( this, aMap );
+ }
+
+ // interface EOFaulting
+
+ /**
+ * Called by EOFaultHandler to prepare the object to be turned into a fault.
+ */
+ public void clearFault()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns this object's EOFaultHandler.
+ */
+ public EOFaultHandler faultHandler()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether this object is currently a fault.
+ * Returns true if this object has not yet retrieved any values.
+ */
+ public boolean isFault()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Turns this object into a fault using the specified fault handler.
+ */
+ public void turnIntoFault( EOFaultHandler aFaultHandler )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Called to completely fire the fault, reading all attributes.
+ * This method may be implemented to call willRead(null).
+ */
+ public void willRead()
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Called to fire the fault for the specified key.
+ * The fault manager is required to populate the specified key
+ * with a value, and may populate any or all of the other values
+ * on this object. A null key will populate all values on the object.
+ * NOTE: This method is not part of the specification.
+ */
+ public void willRead( String aKey )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ // interface EODeferredFaulting
+
+ /**
+ * Returns a fault for the specified deferred fault.
+ */
+ public Object willReadRelationship( Object anObject )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ // interface EORelationshipManipulation
+
+ /**
+ * Adds the specified object to the relationship on this
+ * object specified by the key. For to-one relationships,
+ * this operation is the same as valueForKey.
+ */
+ public void addObjectToPropertyWithKey(
+ Object anObject, String aKey )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Removes the specified object from the relationship on
+ * this object specified by the key. For to-one relationships,
+ * this operation is the same as takeValueForKey with a null
+ * value.
+ */
+ public void removeObjectFromPropertyWithKey(
+ Object anObject, String aKey )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * As addObjectToProperty with key, but also performs the
+ * reciprocal operation on the other side of the relationship.
+ */
+ public void addObjectToBothSidesOfRelationshipWithKey(
+ EORelationshipManipulation anObject, String aKey )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * As removeObjectFromPropertyWithKey with key, but also performs the
+ * reciprocal operation on the other side of the relationship.
+ */
+ public void removeObjectFromBothSidesOfRelationshipWithKey(
+ EORelationshipManipulation anObject, String aKey )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ // interface EOValidation
+
+ /**
+ * Validates this object for delete.
+ * Throws an exception if this object cannot be deleted.
+ * This implementation calls to the class description.
+ */
+ public void validateForDelete()
+ {
+ classDescription().validateObjectForDelete( this );
+ }
+
+ /**
+ * Validates this object for insertion into the external store.
+ * Throws an exception if this object cannot be inserted.
+ * Validations here should be specific to insertion.
+ * This implementation calls validateForSave().
+ */
+ public void validateForInsert()
+ {
+ validateForSave();
+ }
+
+ /**
+ * Validates this object for a commit to the external store.
+ * Throws an exception if this object cannot be committed.
+ * Validations here are not specific to either inserts or updates.
+ * This implementation calls to the class description.
+ */
+ public void validateForSave()
+ {
+ classDescription().validateObjectForSave( this );
+ }
+
+ /**
+ * Validates this object for update to the external store.
+ * Throws an exception if this object cannot be updated.
+ * Validations here should be specific to updates.
+ * This implementation calls validateForSave().
+ */
+ public void validateForUpdate()
+ {
+ validateForSave();
+ }
+}
+
+/*
+ * $Log$
+ * 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.3 2001/12/06 16:42:29 mpowers
+ * Added appropriate constructor.
+ *
+ * Revision 1.2 2001/11/24 17:37:29 mpowers
+ * Implemented static methods.
+ *
+ * Revision 1.1 2001/11/17 17:18:15 mpowers
+ * Initial implementation of EOCustomObject.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java
new file mode 100644
index 0000000..c7e5284
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java
@@ -0,0 +1,164 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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 net.wotonomy.foundation.NSArray;
+
+/**
+* EODataSource is used by EODisplayGroup.fetch() to retrieve
+* a list of objects to display. When a display group has a
+* data source, the display group will use the data source to
+* populate the object list and to create new objects to be
+* displayed in the list, and will update the data source when
+* objects are inserted or removed from the list. <br><br>
+*
+* In certain cases, as when a display group needs to populate
+* a child display group to show a one-to-many relationship,
+* the display group will call dataSourceQualifiedByKey to
+* return a new data source that can vend objects associated
+* with the specified key and then call qualifyWithRelationshipKey
+* to specify the object and key that are the source of the
+* child relationship. <br><br>
+*
+* Concrete subclasses are expected to override fetch() and
+* are required to override insertObject and removeObject.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public abstract class EODataSource
+{
+ /**
+ * Creates a new object. You should call
+ * insertObject() to insert the new object into
+ * this data source.
+ * This implementation attempts to create a new
+ * instance of the class returned by
+ * classDescriptionForObjects().
+ * Override to return an object specific to
+ * your implementation.
+ * @return The newly created object, or null if
+ * new objects are not supported by this data source.
+ * @see #classDescriptionForObjects
+ */
+ public Object createObject ()
+ {
+ Object result = null;
+ EOClassDescription c = classDescriptionForObjects();
+ if ( c != null )
+ {
+ result = c.createInstanceWithEditingContext( editingContext(), null );
+ }
+ return result;
+ }
+
+ /**
+ * Inserts the specified object into this data source.
+ */
+ public abstract void insertObject ( Object anObject );
+
+ /**
+ * Deletes the specified object from this data source.
+ */
+ public abstract void deleteObject ( Object anObject );
+
+ /**
+ * Returns the editing context for this data source,
+ * or null if no editing context is used.
+ * This implementation returns null.
+ */
+ public EOEditingContext editingContext ()
+ {
+ return null;
+ }
+
+ /**
+ * Returns a List containing the objects in this
+ * data source. This implementation returns null.
+ */
+ public NSArray fetchObjects ()
+ {
+ return null;
+ }
+
+ /**
+ * Returns a data source that is capable of
+ * manipulating objects of the type returned by
+ * applying the specified key to objects
+ * vended by this data source.
+ * @see #qualifyWithRelationshipKey
+ */
+ public abstract EODataSource
+ dataSourceQualifiedByKey ( String aKey );
+
+ /**
+ * Restricts this data source to vend those
+ * objects that are associated with the specified
+ * key on the specified object.
+ */
+ public abstract void
+ qualifyWithRelationshipKey (
+ String aKey, Object anObject );
+
+ /**
+ * Returns the description of the class of the
+ * objects that is vended by this data source,
+ * or null if this cannot be determined.
+ * This implementation returns null.
+ */
+ public EOClassDescription
+ classDescriptionForObjects ()
+ {
+ return null;
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.5 2001/05/21 14:02:44 mpowers
+ * Corrected javadoc.
+ *
+ * Revision 1.4 2001/04/27 23:37:20 mpowers
+ * Now using EOClassDescription in the EODataSource class, as we should.
+ *
+ * Revision 1.3 2001/02/27 23:11:07 mpowers
+ * Removed object registration from createObject().
+ *
+ * Revision 1.2 2001/02/16 18:34:19 mpowers
+ * Implementing nested contexts.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:38 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.3 2000/12/20 16:25:34 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java
new file mode 100644
index 0000000..2e350f1
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java
@@ -0,0 +1,339 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.Collection;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSSet;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EODatabaseSource is a general-purpose implementation
+* of EODataSource that is EOClassDescription-aware and
+* that can vend appropriate EODetailDataSources.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public abstract class EODatabaseDataSource
+{
+ EOQualifier auxiliaryQualifier;
+ EOEditingContext editingContext;
+ String entityName;
+ String fetchSpecificationName;
+ EOFetchSpecification fetchSpecification;
+ NSDictionary qualifierBindings;
+ EOClassDescription classDescription;
+ boolean fetchEnabled;
+
+ /**
+ * Constructs a data source that fetches all objects of
+ * the specified entity type.
+ */
+ public EODatabaseDataSource(
+ EOEditingContext aContext, String anEntityName)
+ {
+ this( aContext, anEntityName, null );
+ }
+
+ /**
+ * Constructs a data source that fetches objects of the
+ * specified entity type according to the fetch specification
+ * with the specified name.
+ */
+ public EODatabaseDataSource(
+ EOEditingContext aContext, String anEntityName, String aFetchSpecName)
+ {
+ fetchEnabled = true;
+ editingContext = aContext;
+ entityName = anEntityName;
+ setFetchSpecificationByName( fetchSpecificationName );
+ }
+
+ /**
+ * Returns the qualifier that is applied to the results fetched by the fetch
+ * specification before objects are returned by fetch objects, or null if no
+ * such qualifier has been specified.
+ */
+ public EOQualifier auxiliaryQualifier()
+ {
+ return auxiliaryQualifier;
+ }
+
+ /**
+ * Returns the description of the class of the
+ * objects that is vended by this data source,
+ * or null if no entity name is specified.
+ */
+ public EOClassDescription classDescriptionForObjects ()
+ {
+ if ( entityName == null ) return null;
+ return EOClassDescription.classDescriptionForEntityName( entityName );
+ }
+
+ /**
+ * Returns the object store at the root of the
+ * editing context's editing hierarchy.
+ */
+ public EOObjectStore databaseContext()
+ {
+ EOObjectStore store = editingContext();
+ while ( store instanceof EOEditingContext )
+ {
+ store = ((EOEditingContext)store).parentObjectStore();
+ }
+ return store;
+ }
+
+ /**
+ * Returns a detail data source that is capable of
+ * manipulating objects of the type returned by
+ * applying the specified key to objects
+ * vended by this data source.
+ * @see #qualifyWithRelationshipKey
+ */
+ public EODataSource dataSourceQualifiedByKey ( String aKey )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ /**
+ * Deletes the specified object from this data source.
+ * This implementation deletes the specified object from
+ * the editing context.
+ */
+ public void deleteObject ( Object anObject )
+ {
+ editingContext.deleteObject( anObject );
+ }
+
+ /**
+ * Returns the editing context for this data source,
+ * or null if no editing context was specified.
+ */
+ public EOEditingContext editingContext ()
+ {
+ return editingContext;
+ }
+
+/*
+ public EOEntity entity() {}
+*/
+
+ /**
+ * Returns a List containing the objects of the current
+ * entity type that conform to the specified fetch specification.
+ * If an auxiliary qualifier has been specified, that qualifier
+ * is applied to the objects before returning the result.
+ * If fetch is not enabled, this method returns null.
+ */
+ public NSArray fetchObjects ()
+ {
+ if ( ! isFetchEnabled() ) return null;
+ NSArray result =
+ editingContext.objectsWithFetchSpecification( fetchSpecification() );
+ if ( auxiliaryQualifier() != null )
+ {
+ result = EOQualifier.filteredArrayWithQualifier( result, auxiliaryQualifier() );
+ }
+ return result;
+ }
+
+ /**
+ * Returns the fetch specification currently used by this data
+ * source to fetch objects, or null if none is specified.
+ * If null, this fetchObjects() will return all objects of the
+ * specified entity type.
+ */
+ public EOFetchSpecification fetchSpecification()
+ {
+ return fetchSpecification;
+ }
+
+ /**
+ * Returns a copy of the fetch specification that will be used to
+ * determine fetch for this data source. If this data source has
+ * an auxiliary qualifier, that qualifier will be inserted into
+ * the returned fetch specification's qualifier.
+ */
+ public EOFetchSpecification fetchSpecificationForFetch()
+ {
+ EOFetchSpecification result = (EOFetchSpecification) fetchSpecification.clone();
+ if ( auxiliaryQualifier() != null )
+ {
+ NSMutableArray join = new NSMutableArray();
+ join.addObject( fetchSpecification.qualifier() );
+ join.addObject( auxiliaryQualifier() );
+ result.setQualifier( new EOAndQualifier( join ) );
+ }
+ return result;
+ }
+
+ /**
+ * Returns the name of the current fetch specification, or null
+ * if no name has been specified.
+ */
+ public String fetchSpecificationName()
+ {
+ return fetchSpecificationName;
+ }
+
+ /**
+ * Inserts the specified object into this data source.
+ * This implementation registers the object as an inserted
+ * object with the editing context.
+ */
+ public void insertObject ( Object anObject )
+ {
+ editingContext.insertObject( anObject );
+ }
+
+ /**
+ * Returns whether fetching is currently allowed.
+ * If false, fetchObjects() will return null.
+ * Default is true.
+ */
+ public boolean isFetchEnabled()
+ {
+ return fetchEnabled;
+ }
+
+ /**
+ * Returns a List of the union of the binding keys for the fetch spec's
+ * qualifier and the auxiliary qualifier.
+ */
+ public NSArray qualifierBindingKeys()
+ {
+ NSSet union = new NSSet();
+ if ( ( fetchSpecification != null )
+ && ( fetchSpecification.qualifier() != null ) )
+ {
+ union.addAll( fetchSpecification.qualifier().bindingKeys() );
+ }
+ if ( auxiliaryQualifier() != null )
+ {
+ union.addAll( auxiliaryQualifier().bindingKeys() );
+ }
+ return new NSArray( (Collection) union );
+ }
+
+ /**
+ * Returns a Map of the bindings that will be applied against
+ * the fetch spec's qualifier and the auxiliary qualifier,
+ * or null if no bindings exist.
+ */
+ public NSDictionary qualifierBindings()
+ {
+ if ( qualifierBindings == null ) return null;
+ return new NSDictionary( (Map) qualifierBindings );
+ }
+
+ /**
+ * Restricts this data source to vend those
+ * objects that are associated with the specified
+ * key on the specified object.
+ */
+ public void qualifyWithRelationshipKey (
+ String aKey, Object anObject )
+ {
+ throw new WotonomyException( "Not implemented yet" );
+ }
+
+ /**
+ * Sets the auxiliary qualifier that will be applied to
+ * objects returned from the fetch described by the fetch specification.
+ */
+ public void setAuxiliaryQualifier(EOQualifier aQualifier)
+ {
+ auxiliaryQualifier = aQualifier;
+ }
+
+ /**
+ * Sets whether fetches are currently allowed.
+ * If false, fetchObjects() will return null.
+ */
+ public void setFetchEnabled(boolean isFetchEnabled)
+ {
+ fetchEnabled = isFetchEnabled;
+ }
+
+ /**
+ * Sets the fetch specification used by this data source.
+ * If null, all objects of the specified entity type will
+ * be returned by fetchObjects().
+ */
+ public void setFetchSpecification( EOFetchSpecification aFetchSpec)
+ {
+ fetchSpecificationName = null;
+ fetchSpecification = aFetchSpec;
+ }
+
+ /**
+ * Sets the fetch specification used by this data source,
+ * requesting it from the class description for this data source's
+ * entity class description, if any. If the name cannot be resolved,
+ * the fetch specification will be set to null.
+ */
+ public void setFetchSpecificationByName(String aName)
+ {
+ fetchSpecificationName = aName;
+ fetchSpecification = EOFetchSpecification.fetchSpecificationNamed( aName, entityName );
+ }
+
+ /*
+ public void setParentDataSourceRelationshipKey( EODataSource aDataSource, String aKey)
+ */
+
+ /**
+ * Sets the bindings to be applied to the fetch specification and the auxiliary qualifier.
+ */
+ public void setQualifierBindings(Map aBindingMap)
+ {
+ if ( aBindingMap == null )
+ {
+ qualifierBindings = null;
+ }
+ else
+ {
+ qualifierBindings = new NSDictionary( (Map) aBindingMap );
+ }
+ }
+}
+
+/*
+ * $Log$
+ * 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.1 2001/11/24 17:38:00 mpowers
+ * Contributing EODatabaseDataSource.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java
new file mode 100644
index 0000000..c87a097
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java
@@ -0,0 +1,48 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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;
+
+/**
+* EODeferredFaulting defines a method
+* to handle relationships that are deferred faults.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EODeferredFaulting extends EOFaulting
+{
+ /**
+ * Returns a fault for the specified deferred fault.
+ */
+ Object willReadRelationship( Object anObject );
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2001/11/13 04:13:59 mpowers
+ * Added interfaces needed to begin work on EOCustomObject.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java
new file mode 100644
index 0000000..758fb40
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java
@@ -0,0 +1,152 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 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.Observable;
+import java.util.Observer;
+
+/**
+* This is an abstract class for receiving coalesced
+* notifications of changes from objects.
+* This class also implements Observer for greater
+* compatibility.
+* The point of EODelayedObservers is that when
+* they receive a willChange message, they
+* queue themselves with a EODelayedObserverQueue
+* so they can receive a single subjectChanged()
+* after all changes from an observed object take
+* place.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public abstract class EODelayedObserver
+ implements EOObserving, Observer
+{
+ /**
+ * Notified immediately.
+ */
+ public static final int ObserverPriorityImmediate = 0;
+ public static final int ObserverPriorityFirst = 1;
+ public static final int ObserverPrioritySecond = 2;
+ public static final int ObserverPriorityThird = 3;
+ public static final int ObserverPriorityFourth = 4;
+ public static final int ObserverPriorityFifth = 5;
+ public static final int ObserverPrioritySixth = 6;
+ public static final int ObserverPriorityLater = 7;
+ public static final int ObserverNumberOfPriorities = 8;
+
+ /**
+ * Default constructor.
+ */
+ public EODelayedObserver ()
+ {
+ }
+
+ /**
+ * Removes this observer from the observer queue
+ * for a currently pending notification.
+ */
+ public void discardPendingNotification ()
+ {
+ observerQueue().dequeueObserver( this );
+ }
+
+ /**
+ * Returns the observer queue to which this observer
+ * belongs. This implementation returns the default
+ * EODelayedObserverQueue.
+ * Override to use a different one.
+ */
+ public EODelayedObserverQueue observerQueue ()
+ {
+ return EODelayedObserverQueue.defaultObserverQueue();
+ }
+
+ /**
+ * Returns the priority of this observer in the queue.
+ * This implementation returns ObserverPriorityThird.
+ * Override to be notified before other observers.
+ */
+ public int priority ()
+ {
+ return ObserverPriorityThird;
+ }
+
+ /**
+ * Notifies observer that one or more objects that
+ * it is observing have changed. The observer should
+ * check all objects it is observing for changes.
+ */
+ public abstract void subjectChanged ();
+
+ // interface EOObserving
+
+ /**
+ * Called when the specified object is about to change.
+ * This implementation puts this observer on a
+ * notification queue.
+ */
+ public void objectWillChange ( Object anObject )
+ {
+ observerQueue().enqueueObserver( this );
+ }
+
+ // interface Observer
+
+ /**
+ * Called when the specified object has changed,
+ * with the specified argument.
+ * This method is included for interacting with
+ * the java.lang.Observer pattern.
+ * This implementation simply objectWillChange(anObject)
+ * so that the observer still gets a single subjectChanged
+ * call in response to multiple changes.
+ */
+ public void update ( Observable anObject, Object aValue )
+ {
+ objectWillChange( anObject );
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.2 2001/10/26 18:38:10 mpowers
+ * Reordered priorities.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:38 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.3 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java
new file mode 100644
index 0000000..6b9b9c3
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java
@@ -0,0 +1,323 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 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.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.wotonomy.foundation.NSRunLoop;
+import net.wotonomy.foundation.NSSelector;
+
+/**
+* EODelayedObserverQueue allows EODelayedObservers
+* to receive only one subjectChanged() message
+* after numerous willChange() messages have
+* been sent. Observers are then notified in order
+* of their priority property,
+* so that certain observers can be notified before
+* others for whatever application-specific purpose.
+* This class is not thread-safe and should be used
+* only for single-threaded GUI clients (AWT and Swing).
+* <br><br>
+*
+* Important note: because AWT's event queue does
+* not allow for priority-based scheduling, this
+* class installs a custom event queue, replacing
+* the existing queue on the AWT dispatch thread.
+* We know of no way around this problem.
+* <br><br>
+*
+* Implementation note: this queue relies on the
+* result of equals() for maintaining a set of
+* objects on the queue. If two EODelayedObservers
+* evaluate to the same value using equals(), only
+* one of them will exist on the queue. If this,
+* starts to suck, we can change it.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+
+public class EODelayedObserverQueue
+{
+ /**
+ * The default run loop ordering flushes the delayed observers
+ * up to ObserverPrioritySixth before dispatching the AWT event
+ * queue. ObserverPriorityLater is run last.
+ */
+ public static int FlushDelayedObserversRunLoopOrdering = 400000;
+
+ private static EODelayedObserverQueue
+ defaultObserverQueue = null;
+
+ private static NSSelector runLaterSelector =
+ new NSSelector( "flushObserverQueue",
+ new Class[] { Object.class } );
+
+ private boolean willRunLater;
+ private LinkedList priorityQueue;
+
+ /**
+ * Default constructor.
+ */
+ public EODelayedObserverQueue ()
+ {
+ willRunLater = false;
+ priorityQueue = new LinkedList();
+ }
+
+ /**
+ * Returns the system default observer queue.
+ */
+ public static EODelayedObserverQueue defaultObserverQueue ()
+ {
+ if ( defaultObserverQueue == null )
+ {
+ defaultObserverQueue = new EODelayedObserverQueue();
+ }
+ return defaultObserverQueue;
+ }
+
+ /**
+ * Removes the specified observer from the queue.
+ */
+ public void dequeueObserver (
+ EODelayedObserver anObserver )
+ {
+//System.out.println( "dequeueObserver: " + anObserver );
+ //synchronized ( priorityQueue )
+ //{
+ priorityQueue.remove( anObserver );
+ //}
+ }
+
+ /**
+ * Adds the specified observer to the queue.
+ * An already enqueued observer will not be
+ * added again.
+ * If the observer's priority is
+ * ObserverPriorityImmediate, it will be
+ * notified immediately and not added to the
+ * queue.
+ * Otherwise, the queue sets itself up to
+ * call notifyObserversUpToPriority during the
+ * run loop as specified by
+ * FlushDelayedObserversRunLoopOrdering.
+ */
+ public void enqueueObserver (
+ EODelayedObserver anObserver )
+ {
+ // syntactic glue for Runnables
+ final EODelayedObserver observer = anObserver;
+
+ if ( observer.priority() ==
+ EODelayedObserver.ObserverPriorityImmediate )
+ {
+ // invoke immediately
+ observer.subjectChanged();
+ }
+ else
+ {
+ // place in the delayed observer queue
+
+ //synchronized ( priorityQueue )
+ //{
+ int i = 0;
+ int priority = observer.priority();
+ Object o;
+
+ Iterator iterator = priorityQueue.iterator();
+
+ // scan entire list to ensure we're not already queued
+ while ( iterator.hasNext() )
+ {
+ o = iterator.next();
+ if ( o == observer )
+ {
+ // already queued
+ return;
+ }
+ if ( ((EODelayedObserver)o).priority() > priority )
+ {
+ // insert at this index: break now
+ break;
+ }
+ i++;
+ }
+
+ // if we broke early, we found a threshhold:
+ // continue scanning to ensure we're not already queued
+ while ( iterator.hasNext() )
+ {
+ if ( iterator.next() == observer )
+ {
+ // already queued
+ return;
+ }
+ }
+
+ // insert before items of lower priority,
+ // otherwise insert at end of list.
+ priorityQueue.add( i, observer );
+
+ //}
+ runLater();
+ }
+//System.out.println( "enqueueObserver: " + anObserver + " : " + priorityQueue );
+ }
+
+ /**
+ * Notifies all observers with priority equal to
+ * or greater than the specified priority.
+ */
+ public void notifyObserversUpToPriority ( int priority )
+ {
+//System.out.println( "notifyObserversUpToPriority: priorityQueue size = " + priorityQueue.size() );
+ EODelayedObserver o;
+ while ( ! priorityQueue.isEmpty() )
+ {
+ o = (EODelayedObserver) priorityQueue.getFirst();
+ if ( o.priority() > priority ) break;
+ priorityQueue.removeFirst();
+
+ try
+ {
+ o.subjectChanged();
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error notifying observer: " + o );
+ exc.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Called to ensure that notifyObserversUpToPriority
+ * will be called on the next event loop.
+ */
+ private void runLater()
+ {
+ if ( ! willRunLater )
+ {
+ willRunLater = true;
+ NSRunLoop.currentRunLoop().performSelectorWithOrder(
+ runLaterSelector, this, null, FlushDelayedObserversRunLoopOrdering, null );
+ }
+ }
+
+ /**
+ * This method is called by the event queue run loop
+ * and calls notifyObserversUpToPriority with
+ * ObserverPriorityLater.
+ * NOTE: This method is not part of the specification.
+ */
+ public void flushObserverQueue( Object anObject )
+ {
+//System.out.println( "EODelayedObserverQueue: running" );
+ notifyObserversUpToPriority( EODelayedObserver.ObserverPrioritySixth );
+ if ( ! priorityQueue.isEmpty() )
+ {
+ // assumes all remaining on queue are ObserverPriorityLater
+ NSRunLoop.invokeLater(
+ new PriorityLaterRunnable( new LinkedList( priorityQueue ) ) );
+ priorityQueue.clear();
+ }
+ willRunLater = false;
+ }
+
+ /**
+ * A runnable for dispatching remaining observers running at ObserverPriorityLater.
+ */
+ class PriorityLaterRunnable implements Runnable
+ {
+ List observers;
+
+ public PriorityLaterRunnable( List anObserverList )
+ {
+ observers = anObserverList;
+ }
+
+ public void run()
+ {
+ EODelayedObserver o = null;
+ Iterator i = observers.iterator();
+ while ( i.hasNext() )
+ {
+ try
+ {
+ o = (EODelayedObserver) i.next();
+ o.subjectChanged();
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error notifying observer: " + o );
+ exc.printStackTrace();
+ }
+ }
+ }
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.8 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.7 2002/05/20 15:08:35 mpowers
+ * Optimization for enqueueObserver: we were scanning the entire list anyway;
+ * now we compare priorities and ensure we're not double-queued on same pass.
+ *
+ * Revision 1.6 2002/05/15 13:45:57 mpowers
+ * RunLater now appropriately runs later: at the end of the current awt queue.
+ *
+ * Revision 1.5 2002/03/11 03:18:39 mpowers
+ * Now properly handling ObserverChangesLater.
+ *
+ * Revision 1.4 2001/10/26 18:37:15 mpowers
+ * Now using NSRunLoop instead of AWT EventQueue.
+ *
+ * Revision 1.3 2001/10/22 21:54:16 mpowers
+ * Removed swing dependency in favor of jdk1.3 event queue.
+ * Optimized priority queue population.
+ *
+ * Revision 1.2 2001/10/12 18:01:59 mpowers
+ * Now catching exceptions before they disrupt the awt event queue.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.5 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java
new file mode 100644
index 0000000..b01727d
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java
@@ -0,0 +1,3247 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationCenter;
+import net.wotonomy.foundation.NSRunLoop;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+// swing dependency for undo manager
+//import javax.swing.undo.UndoManager;
+
+/**
+* EOEditingContext provides transactional support for
+* fetching, editing, and committing changes made on a
+* collection of objects to a parent object store. <br><br>
+*
+* EOEditingContext is itself a subclass of EOObjectStore,
+* and this means that EOEditingContexts can use other
+* EOEditingContexts as their parent. However, there
+* still must exist an EOObjectStore as the root of the
+* editing hierarchy that can maintain persistent state.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOEditingContext
+ extends EOObjectStore
+ implements EOObserving
+{
+ /**
+ * Key for the NSNotification posted after this editing context
+ * saves changes. Object of the notification will be this editing
+ * context, and user info will contain InsertedKey, UpdatedKey,
+ * and DeletedKey (keys are defined in EOObjectStore).
+ */
+ public static final String
+ EditingContextDidSaveChangesNotification =
+ "EOEditingContextDidSaveChangesNotification";
+
+ /**
+ * Key for the NSNotification posted after this editing context
+ * observes changes. Object of the notification will be this editing
+ * context, and user info will contain InsertedKey, UpdatedKey, InvalidatedKey,
+ * and DeletedKey (keys are defined in EOObjectStore), however
+ * the objects in the corresponding Lists will be the actual
+ * objects, not their ids.
+ */
+ public static final String
+ ObjectsChangedInEditingContextNotification =
+ "EOObjectsChangedInEditingContextNotification";
+
+ /**
+ * The default run loop ordering processes recent changes
+ * before delayed observers are notified and before dispatching
+ * the AWT event queue.
+ */
+ public static int
+ EditingContextFlushChangesRunLoopOrdering = 300000;
+
+ private static NSSelector runLaterSelector =
+ new NSSelector( "flushRecentChanges",
+ new Class[] { Object.class } );
+
+ private static EOObjectStore defaultParentObjectStore = null;
+ private static double defaultFetchTimestampLag = 0;
+ private static boolean retainsRegisteredObjects = true;
+
+ private EOObjectStore parentStore;
+ private WeakReference delegate;
+ private WeakReference messageHandler;
+ private List editorSet;
+ private double fetchTimestamp;
+ private boolean lockBeforeModify;
+ private boolean propagateDeletesAfterEvent;
+ private boolean stopValidationAfterError;
+ private NSMutableArray insertedObjects;
+ private NSMutableArray insertedObjectsBuffer;
+ private NSArray insertedObjectsProxy;
+ private NSMutableArray updatedObjects;
+ private NSMutableArray updatedObjectsBuffer;
+ private NSArray updatedObjectsProxy;
+ private NSMutableArray deletedObjects;
+ private NSMutableArray deletedObjectsBuffer;
+ private NSArray deletedObjectsProxy;
+ private NSMutableArray deletedIDsBuffer;
+ private NSMutableArray invalidatedObjectsBuffer;
+ private NSMutableArray invalidatedIDsBuffer;
+ private Registrar registrar;
+// private UndoManager undoManager;
+
+ // so we don't have to trouble EOObserverCenter
+ private boolean ignoreChanges;
+
+ // for delayed handling of processRecentChanges
+ private boolean willRunLater;
+
+ // for handling of notifications posted
+ // while we're in the saveChanges method
+ private boolean isInvalidating;
+
+ // for i18n or other customization
+ static protected String MessageChangeConflict =
+ "Another user changed an object you are editing: ";
+
+ /**
+ * Default constructor creates a new editing context
+ * that uses the default object store. If the default
+ * object store has not been set, an exception is thrown.
+ */
+ public EOEditingContext()
+ {
+ this( defaultParentObjectStore() );
+ }
+
+ /**
+ * Creates a new editing context that uses the specified
+ * object store as its parent object store.
+ */
+ public EOEditingContext( EOObjectStore anObjectStore )
+ {
+ if ( anObjectStore == null )
+ {
+ throw new IllegalArgumentException(
+ "A parent object store must be specified." );
+ }
+
+ parentStore = anObjectStore;
+ delegate = null;
+ messageHandler = null;
+ editorSet = new LinkedList();
+ fetchTimestamp = 0;
+ lockBeforeModify = false;
+ propagateDeletesAfterEvent = true;
+ stopValidationAfterError = true;
+ insertedObjects = new NSMutableArray();
+ insertedObjectsBuffer = new NSMutableArray();
+ insertedObjectsProxy = NSArray.arrayBackedByList( insertedObjects );
+ updatedObjects = new NSMutableArray();
+ updatedObjectsBuffer = new NSMutableArray();
+ updatedObjectsProxy = NSArray.arrayBackedByList( updatedObjects );
+ deletedObjects = new NSMutableArray();
+ deletedObjectsBuffer = new NSMutableArray();
+ deletedObjectsProxy = NSArray.arrayBackedByList( deletedObjects );
+ deletedIDsBuffer = new NSMutableArray();
+ invalidatedObjectsBuffer = new NSMutableArray();
+ invalidatedIDsBuffer = new NSMutableArray();
+
+ if ( instancesRetainRegisteredObjects() )
+ {
+ registrar = new Registrar( this );
+ }
+ else
+ {
+ registrar = new WeakRegistrar( this );
+ }
+
+ ignoreChanges = false;
+ willRunLater = false;
+ isInvalidating = false;
+
+ // create undo manager
+ //TODO: this should be NSUndoManager
+// undoManager = new UndoManager();
+
+ // register for notifications
+ NSSelector handleNotification =
+ new NSSelector( "handleNotification",
+ new Class[] { NSNotification.class } );
+ // any from parent store
+ NSNotificationCenter.defaultCenter().addObserver(
+ this, handleNotification, null, parentStore );
+ // global id change from any
+ NSNotificationCenter.defaultCenter().addObserver(
+ this, handleNotification, EOGlobalID.GlobalIDChangedNotification, null );
+//new net.wotonomy.ui.swing.NotificationInspector( null, parentStore );
+ }
+
+ /**
+ * Registers the specified object as an editor for this
+ * context. The object is expected to implement
+ * EOEditingContext.Editor.
+ */
+ public void addEditor ( Object anEditor )
+ {
+ if ( anEditor == null ) return;
+ editorSet.add( new WeakReference( anEditor ) );
+ }
+
+ /**
+ * Returns a read-only List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship, or may return a placeholder array that
+ * will defer the fetch until needed (aka an array fault).
+ * All objects must be registered in the specified editing context.
+ * This implementation calls to its parent object store's
+ * implementation if the requested source object is not
+ * registered in this editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public NSArray arrayFaultWithSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationshipKey,
+ EOEditingContext aContext )
+ {
+ NSArray result = null;
+ Object source = registrar.objectForGlobalID( aGlobalID );
+
+ // if not registered in our context
+ if ( source == null )
+ {
+ // get the object registered into our context
+ result = parentStore.arrayFaultWithSourceGlobalID(
+ aGlobalID, aRelationshipKey, this );
+ }
+ else // source is registered in our context
+ {
+ // get existing value
+ Object value;
+ if ( source instanceof EOKeyValueCoding )
+ {
+ value = ((EOKeyValueCoding)source).storedValueForKey(
+ aRelationshipKey );
+ }
+ else // handle directly
+ {
+ value = EOKeyValueCodingSupport.storedValueForKey(
+ source, aRelationshipKey );
+ }
+
+ if ( value == null )
+ {
+ // do the same as if the source was null
+ result = parentStore.arrayFaultWithSourceGlobalID(
+ aGlobalID, aRelationshipKey, this );
+ }
+ else
+ if ( value instanceof NSArray )
+ {
+ result = (NSArray) value;
+ }
+ else // not NSArray
+ if ( value instanceof Collection )
+ {
+ // convert to NSArray
+ result = new NSArray( (Collection) value );
+ }
+ else
+ {
+ throw new WotonomyException(
+ "Relationship key did not return a collection: "
+ + aGlobalID + " : " + aRelationshipKey );
+ }
+ }
+
+ // if our context is not the specified context
+ if ( aContext != this )
+ {
+ result = (NSArray) clone( this, result, aContext );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a snapshot of the specified object as it
+ * existed when it was last read or committed to the
+ * parent object store.
+ */
+ public NSDictionary committedSnapshotForObject (
+ Object anObject )
+ {
+ byte[] snapshot = (byte[])
+ registrar.getCommitSnapshot( anObject );
+ if ( snapshot == null )
+ {
+ // this object not modified: take a current snapshot
+ snapshot = takeSnapshot( anObject );
+ }
+ return convertSnapshotToDictionary( snapshot );
+ }
+
+ /**
+ * Returns a snapshot of the specified object as it
+ * existed before the edits triggered by the current
+ * event loop were processed.
+ */
+ public NSDictionary currentEventSnapshotForObject (
+ Object anObject )
+ {
+ byte[] result = (byte[])
+ registrar.getCurrentSnapshot( anObject );
+ if ( result == null )
+ {
+ return committedSnapshotForObject( anObject );
+ }
+ return convertSnapshotToDictionary( result );
+ }
+
+ /**
+ * Returns the delegate for this editing context,
+ * or null if no delegate has been set.
+ */
+ public Object delegate ()
+ {
+ if ( delegate == null ) return null;
+ return delegate.get();
+ }
+
+ /**
+ * Deletes the specified object from this editing context.
+ * The editing context marks the object as deleted and
+ * will notify the parent store when changes are committed.
+ */
+ public void deleteObject (
+ Object anObject )
+ {
+ willChange();
+
+ int i;
+ // remove from added objects if necessary
+ i = insertedObjects.indexOfIdenticalObject( anObject );
+ if ( i != NSArray.NotFound )
+ {
+ insertedObjects.removeObjectAtIndex( i );
+
+ // if in the inserted objects buffer
+ int index = insertedObjectsBuffer.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ // remove from inserted objects buffer
+ insertedObjectsBuffer.removeObjectAtIndex( index );
+ }
+
+ // now forget the object ever existed.
+ forgetObject( anObject );
+
+ // we're done
+ return;
+ }
+ else // otherwise add to deleted objects list
+ {
+ deletedObjects.addObject( anObject );
+ }
+
+ // remove from updated objects if necessary
+ i = updatedObjects.indexOfIdenticalObject( anObject );
+ if ( i != NSArray.NotFound )
+ {
+ updatedObjects.removeObjectAtIndex( i );
+ }
+
+ // add to buffer
+ deletedObjectsBuffer.addObject( anObject );
+ deletedIDsBuffer.addObject( globalIDForObject( anObject ) );
+ }
+
+ /**
+ * Returns a read-only List of all objects marked as deleted
+ * in this editing context.
+ */
+ public NSArray deletedObjects ()
+ {
+ return deletedObjectsProxy;
+ }
+
+ /**
+ * Called by child editing contexts when they no longer
+ * need to track the specified id.
+ * This implementation forwards the call to the parent store.
+ */
+ public void editingContextDidForgetObjectWithGlobalID (
+ EOEditingContext aContext,
+ EOGlobalID aGlobalID )
+ {
+ parentStore.editingContextDidForgetObjectWithGlobalID(
+ aContext, aGlobalID );
+ }
+
+ /**
+ * Returns a read-only List of registered editors of this
+ * editing context.
+ */
+ public NSArray editors ()
+ {
+ NSMutableArray result = new NSMutableArray();
+ Object o;
+ Iterator i = editorSet.iterator();
+ while ( i.hasNext() )
+ {
+ o = ((WeakReference)i.next()).get();
+ if ( o != null )
+ {
+ result.addObject( o );
+ }
+ else
+ {
+ i.remove();
+ }
+ }
+ return result;
+ }
+
+/*
+ public static void encodeObjectWithCoder (
+ Object anObject,
+ NSCoder aCoder )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+
+ /**
+ * Returns the object for the specified id.
+ * If the object is registered in in this context
+ * but not in the specified context,
+ * this implementation will create a copy of the object
+ * and register it in the specified context.
+ * Otherwise it will forward the call to the parent
+ * object store.
+ */
+ public /*EOEnterpriseObject*/ Object faultForGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ Object result = registrar.objectForGlobalID( aGlobalID );
+
+ // if not registered in our context
+ if ( result == null )
+ {
+ // get the object registered into our context
+ result = parentStore.faultForGlobalID( aGlobalID, this );
+ }
+
+ // if our context is not the specified context
+ if ( aContext != this )
+ {
+ result = registerClone( aGlobalID, this, result, aContext );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a fault representing an object of
+ * the specified entity type with values from
+ * the specified dictionary.
+ * This implementation calls faultForRawRow
+ * on the parent store.
+ */
+ public Object faultForRawRow (
+ Map aDictionary,
+ String anEntityName )
+ {
+ return parentStore.faultForRawRow(
+ aDictionary, anEntityName, this );
+ }
+
+ /**
+ * Returns a fault representing an object of
+ * the specified entity type with values from
+ * the specified dictionary. The fault should
+ * belong to the specified editing context.
+ * This implementation forwards the call to
+ * the parent store.
+ */
+ public /*EOEnterpriseObject*/ Object faultForRawRow (
+ Map aDictionary,
+ String anEntityName,
+ EOEditingContext aContext )
+ {
+ return parentStore.faultForRawRow(
+ aDictionary, anEntityName, aContext );
+ }
+
+ /**
+ * Returns the fetch timestamp for this editing context.
+ */
+ public double fetchTimestamp ()
+ {
+ return fetchTimestamp;
+ }
+
+ /**
+ * Unregisters the specified object from this editing context,
+ * removing all references to it. Use this method to remove
+ * an object from the context without marking it for deletion.
+ */
+ public void forgetObject (
+ Object anObject )
+ {
+ EOGlobalID id = registrar.globalIDForObject( anObject );
+ if ( id == null )
+ {
+ System.err.println(
+ "EOEditingContext.forgetObject: not registered: " + anObject );
+ return;
+ }
+
+ // unregister object
+ registrar.forgetObject( anObject );
+
+ // remove from all, inserted, updated, and deleted lists
+ int index;
+ index = updatedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ updatedObjects.removeObjectAtIndex( index );
+ }
+ index = insertedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ insertedObjects.removeObjectAtIndex( index );
+ return;
+ }
+ index = deletedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ deletedObjects.removeObjectAtIndex( index );
+ return;
+ }
+
+ // notify parent context
+ parentStore.editingContextDidForgetObjectWithGlobalID( this, id );
+ }
+
+ /**
+ * Returns the id for the specified object, or null
+ * if the object is not registered in this context.
+ */
+ public EOGlobalID globalIDForObject (
+ Object anObject )
+ {
+ return registrar.globalIDForObject( anObject );
+ }
+
+ /**
+ * Returns an array of ids for an array of objects.
+ */
+ private NSArray globalIDsForObjects(
+ List anObjectList )
+ {
+ NSMutableArray result = new NSMutableArray();
+ Iterator it = anObjectList.iterator();
+ while ( it.hasNext() )
+ {
+ result.add( globalIDForObject( it.next() ) );
+ }
+ return result;
+ }
+
+ /**
+ * Returns whether this editing context has changes that
+ * have not yet been committed to the parent object store.
+ */
+ public boolean hasChanges ()
+ {
+ if ( updatedObjects.count() > 0 ) return true;
+ if ( insertedObjects.count() > 0 ) return true;
+ if ( deletedObjects.count() > 0 ) return true;
+ return false;
+ }
+
+ /**
+ * Given a newly instantiated object, this method
+ * initializes its properties to values appropriate
+ * for the specified id. The object should already
+ * belong to the specified editing context.
+ * This method is called to populate faults.
+ * This implementation will try to apply the values
+ * from an object with a matching id in this editing
+ * context if possible, calling to the parent object
+ * store only if such an object is not found.
+ */
+ public void initializeObject (
+ /*EOEnterpriseObject*/ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext )
+ {
+ Object existingObject = registrar.objectForGlobalID( aGlobalID );
+
+ // if not registered in our context
+ if ( existingObject == null )
+ {
+ // get the object registered into our context
+ existingObject = parentStore.faultForGlobalID( aGlobalID, this );
+ }
+
+ if ( aContext == this )
+ {
+ // initialize the object
+ parentStore.initializeObject(
+ /*(EOEnterpriseObject)*/existingObject, aGlobalID, this );
+ }
+ else // ( aContext != this )
+ {
+ // translates child relationships
+ copy( this, existingObject, aContext, anObject );
+ }
+
+ aContext.registrar.setCommitSnapshot( anObject, null );
+ aContext.registrar.setCurrentSnapshot( anObject, null );
+ }
+
+ /**
+ * Inserts the specified object into this editing context.
+ * This implementation calls insertObjectWithGlobalID
+ * with an EOTemporaryGlobalID.
+ */
+ public void insertObject ( Object anObject )
+ {
+ insertObjectWithGlobalID(
+ anObject, new EOTemporaryGlobalID() );
+ }
+
+ /**
+ * Inserts the specified object into this editing context
+ * with the specified id, which is expected to be a
+ * temporary id.
+ */
+ public void insertObjectWithGlobalID (
+ Object anObject,
+ EOGlobalID aGlobalID )
+ {
+ willChange();
+
+ // if this object was marked for deletion
+ int index = deletedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ // don't need to re-register: just update the lists
+
+ // remove from deleted list
+ deletedObjects.removeObjectAtIndex( index );
+
+ // if in the deleted ids buffer
+ index = deletedIDsBuffer.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ // remove from deleted ids buffer
+ deletedIDsBuffer.removeObjectAtIndex( index );
+ }
+
+ // if in the deleted objects buffer
+ index = deletedObjectsBuffer.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound )
+ {
+ // remove from deleted objects buffer
+ deletedObjectsBuffer.removeObjectAtIndex( index );
+ }
+ else // not in the deleted objects buffer
+ {
+ // add to the inserted objects buffer
+ insertedObjectsBuffer.addObject( anObject );
+ }
+
+ // we're done
+ return;
+ }
+
+ // make sure object is not already in editing context
+ if ( objectForGlobalID( aGlobalID ) != null )
+ {
+ throw new WotonomyException(
+ "Tried to insert but object was already registered:"
+ + aGlobalID );
+ }
+
+ // register object
+ recordObject( anObject, aGlobalID );
+
+ // add to inserted list
+ insertedObjects.addObject( anObject );
+ // add to buffer
+ insertedObjectsBuffer.addObject( anObject );
+ }
+
+ /**
+ * Returns a read-only List of the objects that have been
+ * inserted into this editing context.
+ */
+ public NSArray insertedObjects ()
+ {
+ return insertedObjectsProxy;
+ }
+
+ /**
+ * Turn all objects in this editing context into faults,
+ * so that they will be fetched the next time they are
+ * accessed, and calls invalidateObjectsWithGlobalIDs
+ * on the parent object store.
+ */
+ public void invalidateAllObjects ()
+ {
+ // register change so processRecentChanges is called
+ willChange();
+
+ invalidateAllObjectsQuietly();
+
+ // post notification
+ NSNotificationCenter.defaultCenter().postNotification(
+ new NSNotification(
+ InvalidatedAllObjectsInStoreNotification, this ) );
+ }
+
+ /**
+ * Only refaults all objects, does not notify will change
+ * nor post notification, but does call parent store.
+ * Called by invalidateAllObjects() and handleNotification().
+ */
+ private void invalidateAllObjectsQuietly()
+ {
+ // remember the ids
+ NSMutableArray ids = new NSMutableArray( registrar.registeredGlobalIDs() );
+
+ // track of discarded IDs (from inserted objects)
+ NSMutableArray discardedIDs = new NSMutableArray();
+
+ // refault all objects
+ EOGlobalID id;
+ Object o;
+ Enumeration e = ids.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ id = (EOGlobalID) e.nextElement();
+ o = objectForGlobalID( id );
+
+ // some objects may have been manually discarded
+ if ( o != null )
+ {
+ // don't refault newly inserted objects
+ if ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
+ {
+ refaultObject( o, id, this );
+ }
+ else
+ {
+ // discard inserted objects
+ forgetObject( o );
+ discardedIDs.add( id );
+ }
+ invalidatedObjectsBuffer.add( o );
+ }
+ invalidatedIDsBuffer.add( id );
+ }
+ ids.removeAll( discardedIDs );
+
+ // call to parent store (should call this after posting instead?)
+ isInvalidating = true;
+ parentStore.invalidateObjectsWithGlobalIDs( ids );
+ isInvalidating = false;
+ }
+
+ /**
+ * Turns the objects with the specified ids into faults,
+ * so that they will be fetched the next time they are
+ * accessed, and forwards the call to the parent object store.
+ */
+ public void invalidateObjectsWithGlobalIDs (
+ List anArray )
+ {
+ // register change so processRecentChanges is called
+ willChange();
+
+ // call to parent to invalidate objects
+ parentStore.invalidateObjectsWithGlobalIDs( anArray );
+
+ Object o;
+ EOGlobalID id;
+ Iterator it = anArray.iterator();
+ while ( it.hasNext() )
+ {
+ id = (EOGlobalID) it.next();
+ if ( id != null )
+ {
+ o = objectForGlobalID( id );
+ if ( o != null )
+ {
+ Object result = notifyDelegate(
+ "editingContextShouldInvalidateObject",
+ new Class[] { EOEditingContext.class, Object.class, EOGlobalID.class },
+ new Object[] { this, o, id } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ // refault the object
+ refaultObject( o, id, this );
+ invalidatedObjectsBuffer.add( o );
+ invalidatedIDsBuffer.add( id );
+ }
+ }
+ }
+ else
+ {
+ throw new WotonomyException(
+ "Attempted to invalidate a null global id: " + anArray );
+ }
+ }
+
+ }
+/*
+ public boolean invalidatesObjectsWhenFinalized ( )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+
+ public boolean invalidatesObjectsWhenFreed ( )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+ /**
+ * Returns whether the object referenced by the
+ * specified id is locked.
+ * This implementation simply forwards the call to
+ * the parent object store.
+ */
+ public boolean isObjectLockedWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext)
+ {
+ return parentStore.isObjectLockedWithGlobalID(
+ aGlobalID, aContext );
+ }
+
+/*
+ public void lock ()
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+ /**
+ * Locks the specified object in this editing context
+ * by calling lockObjectWithGlobalID on the parent store.
+ */
+ public void lockObject (
+ Object anObject )
+ {
+ parentStore.lockObjectWithGlobalID(
+ globalIDForObject( anObject ), this );
+ }
+
+ /**
+ * Locks the object referenced by the specified id
+ * in the specified editing context.
+ * This implementation simply forwards the call to
+ * the parent object store.
+ */
+ public void lockObjectWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext)
+ {
+ parentStore.lockObjectWithGlobalID(
+ aGlobalID, aContext );
+ }
+
+ /**
+ * Returns whether this editing context attempts to
+ * lock objects when they are first modified.
+ */
+ public boolean locksObjectsBeforeFirstModification ()
+ {
+ return lockBeforeModify;
+ }
+
+ /**
+ * Returns the message handler for this editing context,
+ * or null if no message handler has been set.
+ */
+ public Object messageHandler ()
+ {
+ if ( messageHandler == null ) return null;
+ return messageHandler.get();
+ }
+
+ /**
+ * Returns the object registered in this editing context
+ * for the specified id, or null if that id is not
+ * registered.
+ */
+ public Object objectForGlobalID (
+ EOGlobalID aGlobalID )
+ {
+ return registrar.objectForGlobalID( aGlobalID );
+ }
+
+ /**
+ * Returns a read-only List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship. This method may not return an array fault
+ * because array faults call this method to fetch on demand.
+ * All objects must be registered the specified editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public NSArray objectsForSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationshipKey,
+ EOEditingContext aContext )
+ {
+//System.out.println( "EOEditingContext.objectsForSourceGlobalID: "
+//+ aGlobalID + " : " + aRelationshipKey );
+
+ NSArray result = null;
+
+if ( aContext == this )
+{
+ throw new WotonomyException( "Assert failed: calling objectsForSourceGlobalID on ourself." );
+}
+ Object source = registrar.objectForGlobalID( aGlobalID );
+
+ // if not registered in our context
+ if ( source == null )
+ {
+ // get the object registered into our context
+ result = parentStore.objectsForSourceGlobalID(
+ aGlobalID, aRelationshipKey, this );
+ }
+ else // source is registered in our context
+ {
+ // get existing value
+ Object value;
+ if ( source instanceof EOKeyValueCoding )
+ {
+ value = ((EOKeyValueCoding)source).storedValueForKey(
+ aRelationshipKey );
+ }
+ else // handle directly
+ {
+ value = EOKeyValueCodingSupport.storedValueForKey(
+ source, aRelationshipKey );
+ }
+
+ // if we don't have a valid value on our object
+ if ( ( value == null )
+ || ( ( value instanceof ArrayFault )
+ && ( !((ArrayFault)value).isFetched() ) ) )
+ {
+ // do the same as if the source was null
+ result = parentStore.objectsForSourceGlobalID(
+ aGlobalID, aRelationshipKey, this );
+
+ // set our value since we have it
+ if ( source instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)source).takeStoredValueForKey(
+ result, aRelationshipKey );
+ }
+ else // handle directly
+ {
+ EOKeyValueCodingSupport.takeStoredValueForKey(
+ source, result, aRelationshipKey );
+ }
+ }
+ else
+ if ( ( value instanceof ArrayFault )
+ && ( !((ArrayFault)value).isFetched() ) )
+ {
+ // do the same as if the source was null
+ result = parentStore.objectsForSourceGlobalID(
+ aGlobalID, aRelationshipKey, this );
+ }
+ else
+ if ( value instanceof NSArray )
+ {
+ result = (NSArray) value;
+ }
+ else // not NSArray
+ if ( value instanceof Collection )
+ {
+ // convert to NSArray
+ result = new NSArray( (Collection) value );
+ }
+ else
+ {
+ throw new WotonomyException(
+ "Relationship key did not return a collection: "
+ + aGlobalID + " : " + aRelationshipKey );
+ }
+ }
+
+ // if our context is not the specified context
+ if ( aContext != this )
+ {
+ result = (NSArray) clone( this, result, aContext );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a read-only List of objects the meet the criteria of
+ * the supplied specification. This method simply calls
+ * objectsWithFetchSpecification on this editing context
+ * with this editing context as the parameter.
+ */
+ public NSArray objectsWithFetchSpecification (
+ EOFetchSpecification aFetchSpec )
+ {
+ return objectsWithFetchSpecification( aFetchSpec, this );
+ }
+
+ /**
+ * Returns a read-only List of objects the meet the criteria of
+ * the supplied specification. Faults are not allowed in the array.
+ * If any objects are already fetched, they should not be
+ * refetched. All objects should belong to the specified editing context.
+ * This implementation forwards the call to the parent object
+ * store, which will register each object in the specified editing
+ * context only if it does not already exist.
+ */
+ public NSArray objectsWithFetchSpecification (
+ EOFetchSpecification aFetchSpec,
+ EOEditingContext aContext)
+ {
+ if ( aContext == this )
+ {
+ Object result = notifyDelegate(
+ "editingContextShouldFetchObjects",
+ new Class[] { EOEditingContext.class, EOFetchSpecification.class },
+ new Object[] { aContext, aFetchSpec } );
+ if ( result instanceof NSArray ) return (NSArray) result;
+ }
+ return parentStore.objectsWithFetchSpecification( aFetchSpec, aContext );
+ }
+
+ /**
+ * Returns the parent object store for this editing context.
+ * The result will not be null.
+ */
+ public EOObjectStore parentObjectStore ()
+ {
+ return parentStore;
+ }
+
+ /**
+ * Updates the inserted, updated, and deleted objects lists,
+ * and posts notifications about which objects have been changed.
+ * This method is called at the end of an event loop in which
+ * objects were modified. This method is additionally called
+ * by saveChanges() so that any changes in the same event loop
+ * will be processed correctly before calling to the parent
+ * object store.
+ * This implementation updates those lists immediately, but
+ * only posts notifications when this method is called.
+ */
+ public void processRecentChanges ()
+ { // System.out.println( "EOEditingContext.processRecentChanges: " + invalidatedObjectsBuffer );
+
+ /*
+ * This implementation actually updates those lists immediately,
+ * but keeps a separate buffer of changes in the current event
+ * loop for the purposes of posting a notification.
+ * NOTE: to reenable buffering, uncomment lines from this method
+ * body and reenable the RecentChangesObserver in the constructor.
+ */
+
+ // broadcast ObjectsChangedInStoreNotification
+ // for the benefit of child editing contexts
+
+ boolean postStoreInfo =
+ ( insertedObjectsBuffer.size() +
+ updatedObjectsBuffer.size() +
+ deletedIDsBuffer.size() +
+ invalidatedIDsBuffer.size() > 0 );
+
+ NSMutableDictionary storeInfo = new NSMutableDictionary();
+ if ( postStoreInfo )
+ {
+ storeInfo.setObjectForKey(
+ globalIDsForObjects( insertedObjectsBuffer ),
+ // globalIDsForObjects( insertedObjects ),
+ EOObjectStore.InsertedKey );
+ storeInfo.setObjectForKey(
+ globalIDsForObjects( updatedObjectsBuffer ),
+ // globalIDsForObjects( updatedObjects ),
+ EOObjectStore.UpdatedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) deletedIDsBuffer ),
+ // globalIDsForObjects( deletedObjects ),
+ EOObjectStore.DeletedKey );
+ storeInfo.setObjectForKey(
+ new NSArray( (Collection) invalidatedIDsBuffer ),
+ EOObjectStore.InvalidatedKey );
+ }
+
+ // broadcast ObjectsChangedInEditingContextNotification
+ // for the benefit of attached display groups
+
+ boolean postContextInfo =
+ ( insertedObjectsBuffer.size() +
+ updatedObjectsBuffer.size() +
+ deletedObjectsBuffer.size() +
+ invalidatedObjectsBuffer.size() > 0 );
+
+ NSMutableDictionary contextInfo = new NSMutableDictionary();
+
+ if ( postContextInfo )
+ {
+
+ contextInfo.setObjectForKey(
+ new NSArray( (Collection) insertedObjectsBuffer ),
+ // new NSArray( (Collection) insertedObjects ),
+ EOObjectStore.InsertedKey );
+ contextInfo.setObjectForKey(
+ new NSArray( (Collection) updatedObjectsBuffer ),
+ // new NSArray( (Collection) updatedObjects ),
+ EOObjectStore.UpdatedKey );
+ contextInfo.setObjectForKey(
+ new NSArray( (Collection) deletedObjectsBuffer ),
+ // new NSArray( (Collection) deletedObjects ),
+ EOObjectStore.DeletedKey );
+ contextInfo.setObjectForKey(
+ new NSArray( (Collection) invalidatedObjectsBuffer ),
+ EOObjectStore.InvalidatedKey );
+ }
+
+ // update the current snapshots
+
+ Object o;
+ Iterator it;
+ it = insertedObjectsBuffer.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ registrar.setCurrentSnapshot( o, takeSnapshot( o ) );
+ }
+ it = updatedObjectsBuffer.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ registrar.setCurrentSnapshot( o, takeSnapshot( o ) );
+ }
+
+ // clear buffers
+
+ insertedObjectsBuffer.removeAllObjects();
+ updatedObjectsBuffer.removeAllObjects();
+ deletedObjectsBuffer.removeAllObjects();
+ deletedIDsBuffer.removeAllObjects();
+ invalidatedObjectsBuffer.removeAllObjects();
+ invalidatedIDsBuffer.removeAllObjects();
+
+ // post notifications (does order matter?)
+
+ if ( postStoreInfo )
+ {
+ NSNotificationCenter.defaultCenter().postNotification(
+ new NSNotification(
+ ObjectsChangedInStoreNotification, this, storeInfo ) );
+ }
+
+ if ( postContextInfo )
+ {
+ NSNotificationCenter.defaultCenter().postNotification(
+ new NSNotification(
+ ObjectsChangedInEditingContextNotification, this, contextInfo ) );
+ }
+
+ }
+
+ /**
+ * Returns whether this editing context propagates deletes
+ * immediately after the event that triggered the delete.
+ * Otherwise, propagation occurs only before commit.
+ */
+ public boolean propagatesDeletesAtEndOfEvent ()
+ {
+ return propagateDeletesAfterEvent;
+ }
+
+ /**
+ * Registers the specified object in this editing context
+ * for the specified id. This method is called by an object
+ * store when fetching objects for a display group, or when
+ * objects are inserted into a display group.
+ * This implementation will re-register the object under the
+ * new id if it is already registered under a different id.
+ */
+ public void recordObject (
+ Object anObject,
+ EOGlobalID aGlobalID )
+ {
+ // find state for re-registration
+ boolean inserted = false;
+ boolean updated = false;
+ boolean deleted = false;
+
+ // is the object already registered?
+ EOGlobalID existingID = globalIDForObject( anObject );
+ if ( existingID != null )
+ {
+ // remember object state
+ int index;
+ index = insertedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound ) inserted = true;
+ index = updatedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound ) updated = true;
+ index = deletedObjects.indexOfIdenticalObject( anObject );
+ if ( index != NSArray.NotFound ) deleted = true;
+ // forget the object
+ forgetObject( anObject );
+ }
+
+ // is the global id already in use?
+ Object existingObject = objectForGlobalID( aGlobalID );
+ if ( existingObject != null )
+ {
+ // forget it (don't worry about state?)
+ forgetObject( existingObject );
+ }
+
+ registrar.registerObject( anObject, aGlobalID );
+
+ // restore state if necessary
+ if ( inserted ) insertedObjects.addObject( anObject );
+ if ( updated ) updatedObjects.addObject( anObject );
+ if ( deleted ) deletedObjects.addObject( anObject );
+ }
+
+ /**
+ * Undoes the last undo operation.
+ */
+ public void redo ()
+ {
+ //TODO: not supported yet
+ throw new UnsupportedOperationException("Not implemented yet.");
+ }
+
+ /**
+ * Refaults this editing context, turning all unmodified
+ * objects into faults. This implementation calls
+ * editingContextWillSaveChanges() on all editors, and
+ * then calls refaultObjects().
+ */
+ public void refault ()
+ {
+ fireWillSaveChanges();
+ refaultObjects();
+ }
+
+ /**
+ * Refaults the specified object, turning it into a fault
+ * for the specified global id in the specified context.
+ */
+ public void refaultObject (
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext)
+ {
+ aContext.registrar.setCurrentSnapshot( anObject, null );
+
+ ignoreChanges = true;
+ parentStore.refaultObject( anObject, aGlobalID, aContext );
+ ignoreChanges = false;
+
+ // remove from updated objects if necessary
+ int i = updatedObjects.indexOfIdenticalObject( anObject );
+ if ( i != NSArray.NotFound )
+ {
+ updatedObjects.removeObjectAtIndex( i );
+ }
+
+ // add to invalidated notification queue
+ invalidatedObjectsBuffer.addObject( anObject );
+ invalidatedIDsBuffer.addObject( aGlobalID );
+ }
+
+ /**
+ * Turns all unmodified objects into faults, calling
+ * processRecentChanges() and then refaultObject() for
+ * each unmodified object.
+ */
+ public void refaultObjects ()
+ {
+ // is this call really needed?
+ // processRecentChanges();
+
+ Object o;
+ EOGlobalID id;
+ Iterator it = registeredObjects().iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ if ( ( updatedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
+ && ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
+ && ( deletedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) )
+ {
+ id = globalIDForObject( o );
+ refaultObject( o, id, this );
+ }
+ }
+ }
+
+ /**
+ * Calls editingContextWillSaveChanges() on all editors,
+ * and then calls invalidateAllObjects().
+ */
+ public void refetch ()
+ {
+ fireWillSaveChanges();
+ invalidateAllObjects();
+ }
+
+ /**
+ * Returns a read-only List of all objects registered in this
+ * editing context.
+ */
+ public NSArray registeredObjects ()
+ {
+ return registrar.registeredObjects();
+ }
+
+ /**
+ * Unregisters the specified editor with this editing context.
+ */
+ public void removeEditor ( Object anObject )
+ {
+ if ( anObject == null ) return;
+
+ Object o;
+ Iterator i = editorSet.iterator();
+ while ( i.hasNext() )
+ {
+ o = ((WeakReference)i.next()).get();
+ if ( ( o == null ) || ( o == anObject ) )
+ {
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Unregisters all objects from this editing context,
+ * and resets the fetch timestamp.
+ */
+ public void reset ()
+ {
+ Iterator it = registeredObjects().iterator();
+ while ( it.hasNext() )
+ {
+ forgetObject( it.next() );
+ }
+ fetchTimestamp = 0; //FIXME: reset timestamp properly
+ }
+
+ /**
+ * Reverts the objects in this editing context to
+ * their original state.
+ * Calls editingContextWillSaveChanges on all editors,
+ * discards all inserted objects, restores deleted
+ * objects, and applies the fetch snapshot to all
+ * registered objects.
+ */
+ public void revert ()
+ {
+ willChange();
+ fireWillSaveChanges();
+
+ Iterator it;
+
+ // forget inserted objects
+ it = new NSArray( insertedObjects ).iterator();
+ while ( it.hasNext() )
+ {
+ forgetObject( it.next() );
+ }
+
+ EOGlobalID id;
+ Object o;
+ byte[] snapshot;
+
+ // re-initialize updated objects
+ it = new NSArray( updatedObjects ).iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ snapshot = (byte[]) registrar.getCommitSnapshot( o );
+ if ( snapshot != null )
+ {
+ applySnapshot( snapshot, o );
+ }
+ registrar.setCommitSnapshot( o, null );
+ updatedObjectsBuffer.addObject( o );
+ }
+
+ // re-initialize deleted objects
+ it = new NSArray( deletedObjects ).iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ snapshot = (byte[]) registrar.getCommitSnapshot( o );
+ if ( snapshot != null )
+ {
+ applySnapshot( snapshot, o );
+ }
+ registrar.setCommitSnapshot( o, null );
+ updatedObjectsBuffer.addObject( o );
+ }
+
+ // reset lists
+ insertedObjects.removeAllObjects(); // unneccessary?
+ deletedObjects.removeAllObjects();
+ updatedObjects.removeAllObjects();
+
+ // post notification
+ processRecentChanges();
+ }
+
+ /**
+ * Returns the root object store, which is the parent
+ * of all parent object stores of this editing context.
+ */
+ public EOObjectStore rootObjectStore ()
+ {
+ EOObjectStore parent = parentObjectStore();
+ while ( parent instanceof EOEditingContext )
+ {
+ parent = ((EOEditingContext)parent).parentObjectStore();
+ }
+ return parent;
+ }
+
+ /**
+ * Calls editingContextWillSaveChanges on all editors,
+ * and commits all changes in this editing context to
+ * the parent editing context by calling
+ * saveChangesInEditingContext to the parent.
+ * Then posts EditingContextDidSaveChangeNotification.
+ */
+ public void saveChanges ()
+ {
+//System.out.println( "EOEditingContext.saveChanges: " + this );
+ willChange();
+
+ // process any changes
+ processRecentChanges();
+
+ // set up user info for notification to be posted.
+ NSMutableDictionary userInfo = new NSMutableDictionary();
+ userInfo.setObjectForKey(
+ new NSArray( (Collection) insertedObjects ),
+ EOObjectStore.InsertedKey );
+ userInfo.setObjectForKey(
+ new NSArray( (Collection) updatedObjects ),
+ EOObjectStore.UpdatedKey );
+ userInfo.setObjectForKey(
+ new NSArray( (Collection) deletedObjects ),
+ EOObjectStore.DeletedKey );
+
+ // notify the editors
+ fireWillSaveChanges();
+
+ // notify the delegate
+ notifyDelegate(
+ "editingContextWillSaveChanges",
+ new Class[] { EOEditingContext.class },
+ new Object[] { this } );
+
+ // needed for notification handling
+ isInvalidating = true;
+ try
+ {
+ // ask parent to save us
+ parentStore.saveChangesInEditingContext( this );
+ }
+ catch ( RuntimeException e )
+ {
+ // unset save flag and rethrow
+ isInvalidating = false;
+ throw e;
+ }
+ isInvalidating = false;
+
+ // no exceptions: proceed!
+
+ Object o, key;
+ Iterator it;
+
+ // update the committed snapshots
+ it = insertedObjects.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ registrar.setCommitSnapshot( o, null );
+ registrar.setCurrentSnapshot( o, null );
+ }
+ it = updatedObjects.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ registrar.setCommitSnapshot( o, null );
+ registrar.setCurrentSnapshot( o, null );
+ }
+
+ // clear the lists
+ updatedObjects.removeAllObjects();
+ insertedObjects.removeAllObjects();
+ it = new NSArray( deletedObjects() ).iterator();
+ while ( it.hasNext() )
+ { // parent is doing this as well?
+ forgetObject( it.next() );
+ }
+
+ // post notification
+ NSNotificationCenter.defaultCenter().postNotification(
+ new NSNotification(
+ EditingContextDidSaveChangesNotification, this, userInfo ) );
+ }
+
+ /**
+ * Commits all changes in the specified editing context
+ * to this one. Called by child editing contexts in
+ * their saveChanges() method.
+ */
+ public void saveChangesInEditingContext (
+ EOEditingContext aContext)
+ {
+ Object o;
+ Iterator it;
+
+ // process deletes
+ List deletes = new NSArray( aContext.deletedObjects() );
+ it = deletes.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ EOGlobalID id = aContext.globalIDForObject( o );
+ Object localVersion = objectForGlobalID( id );
+ if ( localVersion == null )
+ {
+ // make a local copy and register it
+ localVersion = registerClone( id, aContext, o, this );
+ if ( localVersion == null )
+ {
+ throw new WotonomyException(
+ "Deleted object could not be serialized: "
+ + id + " : " + o );
+ }
+ }
+ else // we have a local version, copy changes
+ {
+ copy( aContext, o, this, localVersion );
+ // copy marks the object as updated: will be on both lists
+ }
+ // delete our copy -- marks context as changed
+ deleteObject( localVersion );
+ }
+
+ // process inserts - all inserts are new objects
+ List inserts = new NSArray( aContext.insertedObjects() );
+ it = inserts.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ // make a local copy and register it
+ EOGlobalID id = aContext.globalIDForObject( o );
+ willChange(); // need to mark editing context as changed
+ Object localVersion = registerClone( id, aContext, o, this );
+ if ( localVersion == null )
+ {
+ throw new WotonomyException(
+ "Inserted object could not be serialized: "
+ + o );
+ }
+ // insert our copy manually so a new id is not generated
+ insertedObjects.addObject( localVersion );
+ insertedObjectsBuffer.addObject( localVersion );
+ }
+
+ // process updates
+ List updates = new NSArray( aContext.updatedObjects() );
+ it = updates.iterator();
+ while ( it.hasNext() )
+ {
+ willChange(); // need to mark editing context as changed
+ o = it.next();
+ EOGlobalID id = aContext.globalIDForObject( o );
+ Object localVersion = objectForGlobalID( id );
+ if ( localVersion == null )
+ {
+ // make a local copy and register it
+ localVersion = registerClone( id, aContext, o, this );
+ if ( localVersion == null )
+ {
+ throw new WotonomyException(
+ "Updated object could not be serialized: "
+ + id + " : " + o );
+ }
+ if ( id.isTemporary() )
+ {
+ // mark this object as inserted
+ insertedObjects.addObject( localVersion );
+ insertedObjectsBuffer.addObject( localVersion );
+ }
+ else
+ {
+ // mark this object as updated
+ updatedObjects.addObject( localVersion );
+
+ // notify of update only if not on deleted list
+ if ( deletedObjectsBuffer.indexOfIdenticalObject(
+ localVersion ) == NSArray.NotFound )
+ {
+ updatedObjectsBuffer.addObject( localVersion );
+ }
+ }
+ }
+ else // we have a local version, copy changes
+ {
+ copy( aContext, o, this, localVersion );
+ // copy marks the object as updated
+ }
+ }
+
+ }
+
+ /**
+ * Sets the delegate for this editing context.
+ * Note: this implementation retains only a
+ * weak reference to the specified object.
+ */
+ public void setDelegate (
+ Object anObject )
+ {
+ if ( anObject == null ) delegate = null;
+ delegate = new WeakReference( anObject );
+ }
+
+ /**
+ * Sets the fetch timestamp for this editing context.
+ */
+ public void setFetchTimestamp (
+ double aDouble )
+ {
+ fetchTimestamp = aDouble;
+ }
+/*
+ public void setInvalidatesObjectsWhenFinalized (
+ boolean invalidatesObjects )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+
+ public void setInvalidatesObjectsWhenFreed (
+ boolean invalidatesObjects )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+ /**
+ * Sets whether this editing context attempts to
+ * lock objects when they are first modified.
+ * Default is false.
+ */
+ public void setLocksObjectsBeforeFirstModification (
+ boolean locksObjects )
+ {
+ lockBeforeModify = locksObjects;
+ }
+
+ /**
+ * Sets the message handler for this editing context.
+ * Note: this implementation retains only a
+ * weak reference to the specified object.
+ */
+ public void setMessageHandler (
+ Object anObject )
+ {
+ if ( anObject == null ) messageHandler = null;
+ messageHandler = new WeakReference( anObject );
+ }
+
+ /**
+ * Sets whether this editing context propagates deletes
+ * immediately after the event that triggered the delete.
+ * Otherwise, propagation occurs only before commit.
+ * Default is true.
+ */
+ public void setPropagatesDeletesAtEndOfEvent (
+ boolean propagatesDeletes )
+ {
+ propagateDeletesAfterEvent = propagatesDeletes;
+ }
+/*
+ public void setSharedEditingContext (
+ EOSharedEditingContext aSharedEditingContext )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+ /**
+ * Sets whether validation is stopped after the
+ * first error occurs. Otherwise, validation will
+ * continue for all other objects.
+ * Default is true.
+ */
+ public void setStopsValidationAfterFirstError (
+ boolean stopsValidation )
+ {
+ stopValidationAfterError = stopsValidation;
+ }
+
+ /**
+ * Sets the undo manager to be used for this context.
+ * Note: This is currently javax.swing.undo.UndoManager,
+ * until we have a implementation of NSUndoManager.
+ */
+/*
+ public void setUndoManager (
+ UndoManager anUndoManager )
+ {
+ undoManager = anUndoManager;
+ }
+*/
+/*
+ public EOSharedEditingContext sharedEditingContext ()
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+ /**
+ * Returns whether validation is stopped after the
+ * first error occurs. Otherwise, validation will
+ * continue for all other objects.
+ */
+ public boolean stopsValidationAfterFirstError ()
+ {
+ return stopValidationAfterError;
+ }
+
+ /**
+ * Reverts the last change on the undo stack.
+ */
+ public void undo ()
+ {
+ //TODO: not supported yet
+ throw new UnsupportedOperationException("Not implemented yet.");
+ }
+/*
+ public NSUndoManager undoManager ()
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+/**
+ public void unlock ()
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+ /**
+ * Returns a read-only list of all objects marked as modified,
+ * but not inserted or deleted, in this editing context.
+ */
+ public NSArray updatedObjects ()
+ {
+ return updatedObjectsProxy;
+ }
+
+ /**
+ * Notify editors of changes.
+ */
+ private void fireWillSaveChanges()
+ {
+ Object o = null;
+ Iterator i = editors().iterator();
+ while ( i.hasNext() )
+ {
+ try
+ {
+ o = i.next();
+ NSSelector.invoke( "editingContextWillSaveChanges",
+ new Class[] { EOEditingContext.class }, o, this );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ // ignore: not implemented
+ }
+ catch ( Exception exc )
+ {
+ // log to standard error
+ System.err.println( "Error while notifying editor of pending save: " + o );
+ exc.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Handles notifications from parent store, looking for
+ * InvalidatedAllObjectsInStoreNotification and
+ * ObjectsChangedInStoreNotification.
+ * The former causes all objects in this store to be
+ * invalidated.
+ * The latter refaults the invalidated ids, merges changes
+ * from the updated ids, and forgets the deleted ids, then
+ * posts a ObjectsChangedInStoreNotification.
+ * Note: This method is not in the public specification.
+ */
+ public void handleNotification( NSNotification aNotification )
+ { // System.out.println( "EOEditingContext: " + this + " : " + aNotification );
+
+ willChange();
+ if ( InvalidatedAllObjectsInStoreNotification
+ .equals( aNotification.name() ) )
+ {
+ refaultObjects();
+
+ // relay notification
+ NSNotificationCenter.defaultCenter().postNotification(
+ new NSNotification(
+ InvalidatedAllObjectsInStoreNotification, this ) );
+ }
+ else
+ if ( EOGlobalID.GlobalIDChangedNotification
+ .equals( aNotification.name() ) )
+ {
+ NSDictionary userInfo = aNotification.userInfo();
+
+ // if any keys in userInfo are registered ids,
+ // re-register with new permanent values.
+
+ Object o;
+ EOGlobalID id;
+ Enumeration e = userInfo.keyEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ id = (EOGlobalID) e.nextElement();
+ o = objectForGlobalID( id );
+ if ( o != null )
+ {
+ // record object is assumed to handle key updates
+ recordObject( o, (EOGlobalID) userInfo.objectForKey( id ) );
+ }
+ }
+ }
+ else
+ if ( EOObjectStore.ObjectsChangedInStoreNotification
+ .equals( aNotification.name() ) )
+ {
+ //System.out.println( "EOEditingContext.handleNotification: " + aNotification + " : " + this );
+ // post so child contexts are notified
+ willChange();
+
+ Object o;
+ EOGlobalID id;
+ Enumeration e;
+ NSDictionary userInfo = aNotification.userInfo();
+
+ // inserted objects are ignored
+
+ // existing deleted objects are removed
+ NSArray deletes = (NSArray) userInfo.objectForKey(
+ EOObjectStore.DeletedKey );
+ e = deletes.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ id = (EOGlobalID) e.nextElement();
+ o = objectForGlobalID( id );
+ if ( o != null )
+ {
+ //System.out.println( "EOEditingContext: deleted: " + id );
+ forgetObject( o );
+ deletedObjectsBuffer.addObject( o );
+ deletedIDsBuffer.addObject( id );
+ }
+ }
+
+ // existing updated objects are merged
+ NSArray updates = (NSArray) userInfo.objectForKey(
+ EOObjectStore.UpdatedKey );
+ e = updates.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ id = (EOGlobalID) e.nextElement();
+ o = objectForGlobalID( id );
+ if ( o != null )
+ {
+ //System.out.println( "EOEditingContext: updated: " + id );
+ if ( updatedObjects // only update if unchanged
+ .indexOfIdenticalObject( o ) == NSArray.NotFound )
+ {
+ refaultObject( o, id, this );
+ updatedObjectsBuffer.addObject( o );
+ }
+ else
+ {
+ // notify user and/or merge
+ handleUpdateConflict( id, o );
+ }
+ }
+ }
+
+ // existing invalidated objects are refaulted
+ NSArray invalidates = (NSArray) userInfo.objectForKey(
+ EOObjectStore.InvalidatedKey );
+ e = invalidates.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ id = (EOGlobalID) e.nextElement();
+ o = objectForGlobalID( id );
+ if ( o != null )
+ {
+ if ( updatedObjects // only invalidate if unchanged
+ .indexOfIdenticalObject( o ) == NSArray.NotFound )
+ {
+ refaultObject( o, id, this );
+ }
+ else
+ {
+ // notify user and/or merge
+ handleUpdateConflict( id, o );
+ }
+ if ( invalidatedObjectsBuffer
+ .indexOfIdenticalObject( o ) == NSArray.NotFound )
+ {
+ invalidatedObjectsBuffer.addObject( o );
+ }
+ if ( invalidatedIDsBuffer
+ .indexOfIdenticalObject( id ) == NSArray.NotFound )
+ {
+ invalidatedIDsBuffer.addObject( id );
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Called by handleNotification to resolve the case where we have
+ * received notification that another user or context has updated
+ * an object that is currently marked as edited in this context.
+ * This implementation first asks the delegate if it should merge.
+ * If true or no delegate, the changes are merged. True or false,
+ * the delegate is then sent editingContextDidMergeChanges.
+ */
+ private void handleUpdateConflict( EOGlobalID anID, Object anObject )
+ {
+ // if we're causing the invalidation, ignore
+ // (this is probably better handled by the caller...)
+ if ( isInvalidating )
+ {
+ ignoreChanges = true;
+ parentStore.refaultObject( anObject, anID, this );
+ ignoreChanges = false;
+ return;
+ }
+
+ Boolean result = (Boolean) notifyDelegate(
+ "editingContextShouldMergeChangesForObject",
+ new Class[] { EOEditingContext.class, Object.class },
+ new Object[] { this, anObject } );
+
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ // do merge
+ mergeExternalChanges( anID, anObject );
+ }
+ else // Boolean.FALSE
+ {
+ // do nothing: don't lose the user's changes
+ }
+
+ // notify merge did happen
+ notifyDelegate(
+ "editingContextDidMergeChanges",
+ new Class[] { EOEditingContext.class },
+ new Object[] { this } );
+ }
+
+ /**
+ * For the currently modified object with the specified global id,
+ * this method merges changes with the updated version in the parent
+ * object store. This implementation looks at the fetch snapshot
+ * to determine which changes where made by the user, fetches the
+ * updated version of the object, and then determine what external
+ * changes were made. If the changes do not overlap, the original
+ * changes are applied to the updated version. If there is a conflict,
+ * notifies the user of the conflict.
+ */
+ private boolean mergeExternalChanges( EOGlobalID anID, Object anObject )
+ {
+ try
+ {
+ Iterator i;
+ Object key;
+
+ // get fetch snapshot
+ Map fetchSnapshot = committedSnapshotForObject( anObject );
+
+ // get current snapshot
+ Map currentSnapshot = currentEventSnapshotForObject( anObject );
+
+ // diff against fetch snapshot
+ Map currentDiff = new HashMap();
+ i = currentSnapshot.keySet().iterator();
+ while ( i.hasNext() )
+ {
+ key = i.next();
+ if ( ! currentSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) )
+ {
+ currentDiff.put( key, currentSnapshot.get( key ) );
+ }
+ }
+
+ // refault
+ ignoreChanges = true;
+ parentStore.refaultObject( anObject, anID, this );
+ ignoreChanges = false;
+
+ // get updated snapshot
+ Map updatedSnapshot = convertSnapshotToDictionary( takeSnapshot( anObject ) );
+
+ // diff against fetch snapshot
+ Map updatedDiff = new HashMap();
+ i = updatedSnapshot.keySet().iterator();
+ while ( i.hasNext() )
+ {
+ key = i.next();
+ if ( ! updatedSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) )
+ {
+ updatedDiff.put( key, updatedSnapshot.get( key ) );
+ }
+ }
+
+ // determine if there's a conflict
+ boolean proceed = true;
+ Set updatedKeys = updatedDiff.keySet();
+ i = currentDiff.keySet().iterator();
+ while ( i.hasNext() )
+ {
+ if ( updatedKeys.contains( i.next() ) )
+ {
+ proceed = false;
+ break;
+ }
+ }
+
+ // if no conflicts, apply original diff to current object and exit
+ if ( proceed )
+ {
+ KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, currentDiff );
+ return true; // exit!
+ }
+ }
+ catch ( Exception exc )
+ {
+ // log error to standard out
+ exc.printStackTrace();
+ }
+
+ // notify user we're unable to merge
+ notifyMessageHandler( MessageChangeConflict + anObject );
+ return false;
+ }
+
+ /**
+ * Sends the specified message to the message handler.
+ */
+ private void notifyMessageHandler( String aMessage )
+ {
+ Object handler = null;
+ try
+ {
+ handler = messageHandler();
+ if ( handler == null ) return;
+ NSSelector.invoke( "editingContextPresentErrorMessage",
+ new Class[] { EOEditingContext.class, String.class },
+ handler, this, aMessage );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ // ignore: not implemented
+ }
+ catch ( Exception exc )
+ {
+ // log to standard error
+ System.err.println(
+ "Error while notifying message handler: " +
+ handler + " : " + aMessage );
+ exc.printStackTrace();
+ }
+ }
+
+ /**
+ * Sends the specified message to the delegate.
+ * Returns the return value of the method,
+ * or null if no return value or no delegate
+ * or no implementation.
+ */
+ private Object notifyDelegate(
+ String aMethodName, Class[] types, Object[] params )
+ {
+ try
+ {
+ Object delegate = delegate();
+ if ( delegate == null ) return null;
+ return NSSelector.invoke(
+ aMethodName, types, delegate, params );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ // ignore: not implemented
+ }
+ catch ( Exception exc )
+ {
+ // log to standard error
+ System.err.println(
+ "Error while messaging delegate: " +
+ delegate + " : " + aMethodName );
+ exc.printStackTrace();
+ }
+
+ return null;
+ }
+
+ // interface EOObserving
+
+ /**
+ * Implementation of the EOObserving interface.
+ * Called before objects are modified.
+ */
+ public void objectWillChange (
+ Object anObject )
+ {
+ if ( ignoreChanges ) return;
+//NSNotificationCenter.defaultCenter().postNotification( "objectWillChange", this, new NSDictionary( "object", anObject ) );
+//new RuntimeException().printStackTrace();
+
+ willChange();
+
+ // mark as updated if not marked already
+ int i = updatedObjects.indexOfIdenticalObject( anObject );
+ if ( i == NSArray.NotFound )
+ {
+ // don't mark inserted objects as updated
+ i = insertedObjects.indexOfIdenticalObject( anObject );
+ if ( i == NSArray.NotFound )
+ {
+ i = deletedObjects.indexOfIdenticalObject( anObject );
+ if ( i == NSArray.NotFound )
+ {
+ // add object
+ updatedObjects.addObject( anObject );
+
+ // record revert snapshot
+ registrar.setCommitSnapshot( anObject, takeSnapshot( anObject ) );
+ }
+ }
+ }
+
+ // add to buffer
+ if ( updatedObjectsBuffer.indexOfIdenticalObject( anObject )
+ == NSArray.NotFound )
+ {
+ updatedObjectsBuffer.addObject( anObject );
+ }
+ }
+
+ // static methods
+
+ public static double defaultFetchTimestampLag ()
+ {
+ return defaultFetchTimestampLag;
+ }
+
+ /**
+ * Returns the default parent object store for all
+ * object stores created with the parameterless
+ * constructor.
+ */
+ public static EOObjectStore defaultParentObjectStore ()
+ {
+ return defaultParentObjectStore;
+ }
+
+/*
+ public static Object initObjectWithCoder (
+ Object anObject,
+ NSCoder aCoder )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+
+ /**
+ * Returns whether editing contexts are configured to retain strong
+ * references to their registered objects. If false, editing contexts
+ * will only retain weak references to their registered objects.
+ */
+ public static boolean instancesRetainRegisteredObjects()
+ {
+ return retainsRegisteredObjects;
+ }
+
+ /**
+ * Sets the global default fetch timestamp lag.
+ */
+ public static void setDefaultFetchTimestampLag (
+ double aDouble )
+ {
+ defaultFetchTimestampLag = aDouble;
+ }
+
+ /**
+ * Sets the global default parent object store,
+ * used for the parameterless constructor.
+ */
+ public static void setDefaultParentObjectStore (
+ EOObjectStore anObjectStore )
+ {
+ defaultParentObjectStore = anObjectStore;
+ }
+
+ public static void setInstancesRetainRegisteredObjects (
+ boolean retainsObjects )
+ {
+ retainsRegisteredObjects = retainsObjects;
+ }
+
+/*
+ public static void setSubstitutionEditingContext (
+ EOEditingContext aContext)
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+
+ public static void setUsesContextRelativeEncoding (
+ boolean usesRelativeEncoding )
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+
+ public static EOEditingContext substitutionEditingContext ()
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+
+ public static boolean usesContextRelativeEncoding ()
+ {
+ throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
+ }
+*/
+
+ public String toString()
+ {
+ return "[EOEditingContext@"+Integer.toHexString(System.identityHashCode(this))+":"+
+ " inserted="+idsForObjects(insertedObjects)+
+ " updated="+idsForObjects(updatedObjects)+
+ " deleted="+idsForObjects(deletedObjects)+
+ " registered="+registrar.registeredGlobalIDs()+" ]";
+ }
+ private List idsForObjects( List objects )
+ {
+ List result = new LinkedList();
+ Iterator i = objects.iterator();
+ while ( i.hasNext() ) result.add( globalIDForObject( i.next() ) );
+ return result;
+ }
+
+ // snapshots
+
+ /**
+ * Returns a NSDictionary containing only the mutable properties
+ * for the specified object and deep clones of their values.
+ * Nulls are represented by NSNull.nullValue().
+ */
+ private byte[] takeSnapshot( Object anObject )
+ { // System.out.println( "takeSnapshot: " + anObject );
+ return KeyValueCodingUtilities.freeze( anObject, this, anObject, true );
+ }
+
+ /**
+ * Applies the map of properties and values to the
+ * specified object. Null values for properties must
+ * be represented by the NSNull.nullValue().
+ * Posts a willChange event before applying changes.
+ */
+ private void applySnapshot( byte[] aSnapshot, Object anObject )
+ {
+ // must clone snapshot to avoid changing existing snapshot
+ NSDictionary values = convertSnapshotToDictionary( aSnapshot );
+
+//System.out.println( "applySnapshot: " + aSnapshot + " : " + values );
+//ignoreChanges = true;
+ willChange();
+ KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, values );
+//ignoreChanges = false;
+ }
+
+ /**
+ * Snapshots are stored internally in binary format,
+ * but exposed to the user as NSDictionaries.
+ */
+ private NSDictionary convertSnapshotToDictionary( byte[] aSnapshot )
+ {
+ // get the object
+ Object clone = KeyValueCodingUtilities.thaw( aSnapshot, this, true );
+ // get all keys for this object
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForClass( clone.getClass() );
+ List keys = new LinkedList();
+ keys.addAll( classDesc.attributeKeys() );
+ keys.addAll( classDesc.toOneRelationshipKeys() );
+ keys.addAll( classDesc.toManyRelationshipKeys() );
+
+ return KeyValueCodingUtilities.valuesForKeys( clone, keys );
+ }
+ /**
+ * Creates a deep clone of the specified object.
+ * (Object.clone() only creates a shallow clone.)
+ * Returns null if operation fails.
+ */
+ static private Object clone(
+ EOEditingContext aSourceContext,
+ Object aSource,
+ EOEditingContext aDestinationContext )
+ { // System.out.println( "clone: " + aSource );
+ return KeyValueCodingUtilities.clone(
+ aSourceContext, aSource, aDestinationContext );
+ }
+
+ /**
+ * Creates a deep clone of the specified object.
+ * but does not transpose references. This allows
+ * us to register an object before transposing
+ * references so that child objects will be able
+ * to resolve references to their parent.
+ * After recording the object, we copy the source
+ * object into the clone, which does transpose
+ * and resolve properly.
+ * Returns null if operation fails.
+ */
+ static private Object registerClone(
+ EOGlobalID aGlobalID,
+ EOEditingContext aSourceContext,
+ Object aSource,
+ EOEditingContext aDestinationContext )
+ {
+ // while we'd like to just transpose/clone at the same time
+ // we must record a clone without transposing: this
+ // avoids a endless loop if the object graph has a cycle
+ Object clone = KeyValueCodingUtilities.thaw(
+ KeyValueCodingUtilities.freeze(
+ aSource, aSourceContext, aSource, false ),
+ aDestinationContext, false );
+
+ aDestinationContext.recordObject( clone, aGlobalID );
+
+ // need to copy to transpose references into this context
+ // while preserving the same instance of the object
+ aDestinationContext.ignoreChanges = true;
+ copy( aSourceContext, aSource, aDestinationContext, clone );
+ aDestinationContext.ignoreChanges = false;
+
+ // return our clone
+ return clone;
+ }
+
+ /**
+ * Copies values from one object to another.
+ * Returns the destination object, or throws exception
+ * if operation fails.
+ */
+ static private Object copy(
+ EOEditingContext aSourceContext,
+ Object aSource,
+ EOEditingContext aDestinationContext,
+ Object aDestination )
+ { // System.out.println( "copy: " );
+ EOObserverCenter.notifyObserversObjectWillChange( aDestination );
+ KeyValueCodingUtilities.copy( aSourceContext, aSource, aDestinationContext, aDestination );
+ return aDestination;
+ }
+
+ // process recent changes
+
+ /**
+ * Called to notify observers of changes.
+ * Also calls runLater().
+ */
+ private void willChange()
+ {
+ EOObserverCenter.notifyObserversObjectWillChange( this );
+ runLater();
+ }
+
+ /**
+ * Called to ensure that processRecentChanges
+ * will be called on the next event loop.
+ */
+ private void runLater()
+ {
+ if ( ! willRunLater )
+ {
+ willRunLater = true;
+ NSRunLoop.currentRunLoop().performSelectorWithOrder(
+ runLaterSelector, this, null, EditingContextFlushChangesRunLoopOrdering, null );
+ }
+ }
+
+ /**
+ * This method is called by the event queue run loop
+ * and calls processRecentChanges.
+ * NOTE: This method is not part of the specification.
+ */
+ public void flushRecentChanges( Object anObject )
+ {
+//System.out.println( "EODelayedObserverQueue: running" );
+ processRecentChanges();
+ willRunLater = false;
+ }
+
+ // inner classes
+
+ /**
+ * Gatekeeper for all access to registered objects.
+ */
+ static private class Registrar
+ {
+ EOEditingContext context;
+ NSMutableDictionary IDsToObjects;
+ NSMutableDictionary objectsToIDs;
+ NSMutableDictionary objectsToCommitSnapshots;
+ NSMutableDictionary objectsToCurrentSnapshots;
+
+ ReferenceKey comparisonKey; //FIXME not thread safe!
+
+ public Registrar( EOEditingContext aContext )
+ {
+ context = aContext;
+ IDsToObjects = new NSMutableDictionary();
+ objectsToIDs = new NSMutableDictionary();
+ objectsToCommitSnapshots = new NSMutableDictionary();
+ objectsToCurrentSnapshots = new NSMutableDictionary();
+ comparisonKey = new ReferenceKey();
+ }
+
+ public NSArray registeredObjects()
+ {
+ return IDsToObjects.allValues();
+ }
+
+ public NSArray registeredGlobalIDs()
+ {
+ return IDsToObjects.allKeys();
+ }
+
+ public Object objectForGlobalID( EOGlobalID aGlobalID )
+ {
+ return IDsToObjects.objectForKey( aGlobalID );
+ }
+
+ public EOGlobalID globalIDForObject( Object anObject )
+ {
+ comparisonKey.set( anObject );
+ return (EOGlobalID) objectsToIDs.objectForKey( comparisonKey );
+ }
+
+ public byte[] getCommitSnapshot( Object anObject )
+ {
+ comparisonKey.set( anObject );
+ return (byte[]) objectsToCommitSnapshots.objectForKey( comparisonKey );
+ }
+
+ public void setCommitSnapshot( Object anObject, byte[] aSnapshot )
+ {
+ if ( aSnapshot == null )
+ {
+ comparisonKey.set( anObject );
+ objectsToCommitSnapshots.removeObjectForKey( comparisonKey );
+ }
+ else
+ {
+ objectsToCommitSnapshots.setObjectForKey(
+ aSnapshot, new ReferenceKey( anObject ) );
+ }
+ }
+
+ public byte[] getCurrentSnapshot( Object anObject )
+ {
+ comparisonKey.set( anObject );
+ return (byte[]) objectsToCurrentSnapshots.objectForKey( comparisonKey );
+ }
+
+ public void setCurrentSnapshot( Object anObject, byte[] aSnapshot )
+ {
+ if ( aSnapshot == null )
+ {
+ comparisonKey.set( anObject );
+ objectsToCurrentSnapshots.removeObjectForKey( comparisonKey );
+ }
+ else
+ {
+ objectsToCurrentSnapshots.setObjectForKey(
+ aSnapshot, new ReferenceKey( anObject ) );
+ }
+ }
+
+ public void registerObject( Object anObject, EOGlobalID aGlobalID )
+ {
+ IDsToObjects.setObjectForKey( anObject, aGlobalID );
+ objectsToIDs.setObjectForKey( aGlobalID, new ReferenceKey( anObject ) );
+ EOObserverCenter.addObserver( context, anObject );
+ }
+
+ public void forgetObject( Object anObject )
+ {
+ comparisonKey.set( anObject );
+ Object id = objectsToIDs.objectForKey( comparisonKey );
+ IDsToObjects.removeObjectForKey( id );
+ objectsToIDs.removeObjectForKey( comparisonKey );
+ EOObserverCenter.removeObserver( context, anObject );
+ }
+
+ public void disposeSnapshots( Object anObject )
+ {
+ setCommitSnapshot( anObject, null );
+ setCurrentSnapshot( anObject, null );
+ }
+
+ }
+
+ /**
+ * Registrar that uses only WeakReferences.
+ * Used if retainsRegisteredObjects is false.
+ */
+ static private class WeakRegistrar extends Registrar
+ {
+ private WeakReferenceKey weakComparisonKey; //FIXME not thread safe!
+
+ public WeakRegistrar( EOEditingContext aContext )
+ {
+ super( aContext );
+ weakComparisonKey = new WeakReferenceKey();
+ }
+
+ public NSArray registeredObjects()
+ {
+ Object object;
+ WeakReferenceKey weakKey;
+ NSMutableArray result = new NSMutableArray();
+ Enumeration e = new NSArray( objectsToIDs.allKeys() ).objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ weakKey = (WeakReferenceKey) e.nextElement();
+ object = weakKey.get();
+ if ( object != null )
+ {
+ result.addObject( object );
+ }
+ else
+ {
+ // object has been released: perform cleanup
+ disposeObject( null, weakKey );
+ }
+ }
+ return result;
+ }
+
+ public Object objectForGlobalID( EOGlobalID aGlobalID )
+ {
+ WeakReference ref = (WeakReference) super.objectForGlobalID( aGlobalID );
+ if ( ref == null ) return null;
+ Object result = ref.get();
+ if ( result == null )
+ {
+ // clean up manually
+ IDsToObjects.removeObjectForKey( aGlobalID );
+ Iterator i = new LinkedList( objectsToIDs.allKeysForObject( ref ) ).iterator();
+ while ( i.hasNext() )
+ {
+ objectsToIDs.removeObjectForKey( i.next() );
+ }
+ disposeSnapshots( aGlobalID );
+ }
+ return result;
+ }
+
+ public byte[] getCommitSnapshot( Object anObject )
+ {
+ weakComparisonKey.set( anObject );
+ return (byte[]) objectsToCommitSnapshots.objectForKey( weakComparisonKey );
+ }
+
+ public void setCommitSnapshot( Object anObject, byte[] aSnapshot )
+ {
+ if ( aSnapshot == null )
+ {
+ weakComparisonKey.set( anObject );
+ objectsToCommitSnapshots.removeObjectForKey( weakComparisonKey );
+ }
+ else
+ {
+ objectsToCommitSnapshots.setObjectForKey(
+ aSnapshot, new WeakReferenceKey( anObject ) );
+ }
+ }
+
+ public byte[] getCurrentSnapshot( Object anObject )
+ {
+ weakComparisonKey.set( anObject );
+ return (byte[]) objectsToCurrentSnapshots.objectForKey( weakComparisonKey );
+ }
+
+ public void setCurrentSnapshot( Object anObject, byte[] aSnapshot )
+ {
+ if ( aSnapshot == null )
+ {
+ weakComparisonKey.set( anObject );
+ objectsToCurrentSnapshots.removeObjectForKey( weakComparisonKey );
+ }
+ else
+ {
+ objectsToCurrentSnapshots.setObjectForKey(
+ aSnapshot, new WeakReferenceKey( anObject ) );
+ }
+ }
+
+ public void registerObject( Object anObject, EOGlobalID aGlobalID )
+ { // new net.wotonomy.ui.swing.ReferenceInspector( anObject );
+ IDsToObjects.setObjectForKey( new WeakReference( anObject ), aGlobalID );
+ objectsToIDs.setObjectForKey( aGlobalID, new WeakReferenceKey( anObject ) );
+ EOObserverCenter.addObserver( context, anObject );
+ }
+
+ public void forgetObject( Object anObject )
+ {
+ disposeObject( anObject, null );
+ }
+
+ // must specify one or the other
+ private void disposeObject( Object anObject, WeakReferenceKey key )
+ {
+ if ( key == null ) key = new WeakReferenceKey( anObject );
+ EOGlobalID id = (EOGlobalID) objectsToIDs.objectForKey( key );
+ if ( id != null ) IDsToObjects.removeObjectForKey( id );
+ objectsToIDs.removeObjectForKey( key );
+ disposeSnapshots( id );
+ if ( anObject != null )
+ {
+ EOObserverCenter.removeObserver( context, anObject );
+ }
+ }
+ }
+
+ /**
+ * Private class used to force a hashmap to
+ * perform key comparisons by reference.
+ */
+ static private class ReferenceKey
+ {
+ int hashCode;
+ Object referent;
+
+ public ReferenceKey()
+ {
+ referent = null;
+ hashCode = -1;
+ }
+
+ public ReferenceKey( Object anObject )
+ {
+ set( anObject );
+ }
+
+ public Object get()
+ {
+ return referent;
+ }
+
+ public void set( Object anObject )
+ {
+ referent = anObject;
+ hashCode = anObject.hashCode();
+ }
+
+ /**
+ * Returns the actual key's hash code.
+ */
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ /**
+ * Compares by reference.
+ */
+ public boolean equals( Object anObject )
+ {
+ if ( anObject == this ) return true;
+ if ( anObject instanceof ReferenceKey )
+ {
+ return ((ReferenceKey)anObject).get() == referent;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Private class used to force a hashmap to
+ * perform key comparisons by reference.
+ */
+ static private class WeakReferenceKey extends ReferenceKey
+ {
+ public WeakReferenceKey()
+ {
+ super();
+ }
+
+ public WeakReferenceKey( Object anObject )
+ {
+ super( anObject );
+ }
+
+ public Object get()
+ {
+ return ((WeakReference)referent).get();
+ }
+
+ public void set( Object anObject )
+ {
+ referent = new WeakReference( anObject );
+ hashCode = anObject.hashCode();
+ }
+
+ /**
+ * Compares by reference.
+ */
+ public boolean equals( Object anObject )
+ {
+ if ( anObject == this ) return true;
+ if ( anObject instanceof ReferenceKey )
+ {
+ return ((ReferenceKey)anObject).get() == get();
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Key combining an object with a string.
+ * Object is compared by reference.
+ */
+ static private class CompoundKey
+ {
+ private Object object;
+ private String string;
+ private int hashCode;
+
+ /**
+ * Creates compound key.
+ * Neither name nor object may be null.
+ */
+ public CompoundKey (
+ Object anObject, String aString )
+ {
+ object = anObject;
+ string = aString;
+ hashCode = object.hashCode() + string.hashCode();
+ }
+
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ public boolean equals( Object anObject )
+ {
+ if ( anObject instanceof CompoundKey )
+ {
+ CompoundKey key = (CompoundKey) anObject;
+ return ( ( key.object == object ) && ( key.string.equals( string ) ) );
+ }
+ return false;
+ }
+
+ public String toString()
+ {
+ return "[CompoundKey:"+object+":"+string+"]";
+ }
+ }
+
+ /**
+ * Used by EditingContext to delegate behavior to another class.
+ * Note that EditingContext doesn't require its delegates to implement
+ * this interface: rather, this interface defines the methods that
+ * EditingContext will attempt to invoke dynamically on its delegate.
+ * The delegate may choose to implement only a subset of the methods
+ * on the interface.
+ */
+ public interface Delegate
+ {
+ /**
+ * Called after the editing context has completed merge operations
+ * on one or more objects after receiving an ObjectChangedInStore
+ * notification.
+ */
+ void editingContextDidMergeChanges(
+ EOEditingContext anEditingContext );
+
+ /**
+ * Called by objectsWithFetchSpecification.
+ * If null, the editing context will pass the fetch specification
+ * on to its parent store, as normal. Otherwise, the context
+ * will use the returned array to service the request.
+ */
+ NSArray editingContextShouldFetchObjects(
+ EOEditingContext anEditingContext,
+ EOFetchSpecification fetchSpecification );
+
+ /**
+ * Called to determine whether an object should be invalidated.
+ * Return false to prevent the object from being invalidated.
+ * Default is true.
+ */
+ boolean editingContextShouldInvalidateObject(
+ EOEditingContext anEditingContext,
+ Object anObject,
+ EOGlobalID aGlobalID );
+
+ /**
+ * Called to determine whether the editing context should attempt
+ * to merge changes in the specified object that the parent store
+ * says has changed via an ObjectChangedInStore notification.
+ * Default is true. Return false if you wish to handle the merge
+ * yourself, by extracting the values in the object now and comparing
+ * them to the values when editingContextDidMergeChanges is called.
+ */
+ boolean editingContextShouldMergeChangesForObject(
+ EOEditingContext anEditingContext,
+ Object anObject );
+
+ /**
+ * Returns whether the editing context should ask its message handler
+ * to display a message. Return false if the delegate will display the error.
+ * Default is true.
+ */
+ boolean editingContextShouldPresentException(
+ EOEditingContext anEditingContext,
+ Throwable exception );
+
+ /**
+ * Returns whether the editing context should undo the most
+ * recent set of changes that resulted in a validation failure.
+ * Default is true.
+ */
+ boolean editingContextShouldUndoUserActionsAfterFailure(
+ EOEditingContext anEditingContext );
+
+ /**
+ * Returns whether the editing context should validate the
+ * most recent set of changes. Default is true.
+ */
+ boolean editingContextShouldValidateChanges(
+ EOEditingContext anEditingContext );
+
+ /**
+ * Called before the editing context saves its changes
+ * to the parent object store.
+ */
+ void editingContextWillSaveChanges(
+ EOEditingContext anEditingContext );
+
+ }
+
+ /**
+ * Editors register themselves with the editing context
+ * so that they may receive notification before the context
+ * commits changes. This is useful for associations whose
+ * components do not immediately commit their changes to
+ * the object they are editing.
+ */
+ public interface Editor
+ {
+ /**
+ * Called before the editing context saves its changes
+ * to the parent object store.
+ */
+ void editingContextWillSaveChanges(
+ EOEditingContext anEditingContext );
+
+ /**
+ * Called to determine whether this editor has changes
+ * that have not been committed to the object in the context.
+ */
+ boolean editorHasChangesForEditingContext(
+ EOEditingContext anEditingContext );
+
+ }
+
+ /**
+ * Used by EditingContext to delegate messaging handling to another class,
+ * typically the display group that has the currently active association.
+ * Note that EditingContext doesn't require its message handlers to implement
+ * this interface: rather, this interface defines the methods that
+ * EditingContext will attempt to invoke dynamically on its delegate.
+ * The delegate may choose to implement only a subset of the methods
+ * on the interface.
+ */
+ public interface MessageHandler
+ {
+ /**
+ * Called to display a message for an error that occurred
+ * in the specified editing context.
+ */
+ void editingContextPresentErrorMessage(
+ EOEditingContext anEditingContext,
+ String aMessage );
+
+ /**
+ * Called by the specified object store to determine whether
+ * fetching should continue, where count is the current count
+ * and limit is the limit as specified by the fetch specification.
+ * Default is false.
+ */
+ boolean editingContextShouldContinueFetching(
+ EOEditingContext anEditingContext,
+ int count,
+ int limit,
+ EOObjectStore anObjectStore );
+
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.86 2003/12/18 15:37:38 mpowers
+ * Changes to retain ability to work with objects that don't necessarily
+ * implement EOEnterpriseObject. I would still like to preserve this case
+ * for general usage, however the access package is free to assume that
+ * those objects will be EOs and cast appropriately.
+ *
+ * Revision 1.85 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.84 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.83 2003/02/13 15:24:33 mpowers
+ * hasChanges is now derived, not tracked.
+ * refaultObject now more consistently removes object from updated list.
+ *
+ * Revision 1.82 2002/12/16 15:46:00 mpowers
+ * Major refactoring to implement setInstancesRetainRegisteredObjects().
+ *
+ * Revision 1.81 2002/11/18 22:10:58 mpowers
+ * Now resetting hasChanges flag on reset.
+ *
+ * Revision 1.80 2002/10/24 21:15:33 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.79 2002/10/24 18:18:12 mpowers
+ * NSArray's are now considered read-only, so we can return our internal
+ * representation to reduce unnecessary object allocation.
+ *
+ * Revision 1.78 2002/06/21 21:44:33 mpowers
+ * No longer marking deleted objects as updated (thanks to dwang).
+ *
+ * Revision 1.77 2002/05/20 15:10:17 mpowers
+ * No longer refaulting if delegate does not wish to handle the merge.
+ *
+ * Revision 1.76 2002/03/26 21:46:06 mpowers
+ * Contributing EditingContext as a java-friendly convenience.
+ *
+ * Revision 1.75 2002/03/06 16:14:57 mpowers
+ * More attempts at ignoring update conflicts that come from ourself.
+ *
+ * Revision 1.74 2002/02/21 21:57:50 mpowers
+ * Implemented default merge behavior.
+ *
+ * Revision 1.73 2002/02/20 16:46:54 mpowers
+ * Implemented better support for EOEditingContext.Delegate.
+ *
+ * Revision 1.70 2002/02/19 22:26:05 mpowers
+ * Implemented EOEditingContext.MessageHandler support.
+ *
+ * Revision 1.69 2002/02/19 16:33:42 mpowers
+ * Implemented support for EditingContext.Editor
+ *
+ * Revision 1.68 2002/02/13 22:00:34 mpowers
+ * Fixed: invalidateAllObjects tries to invalidate inserted objects,
+ * typically causing class cast exceptions involving EOTemporaryGlobalID.
+ *
+ * Revision 1.67 2002/02/06 21:15:35 mpowers
+ * No longer refaulting a dirty object when we receive an invalidation notif.
+ *
+ * Revision 1.66 2002/01/08 19:31:03 mpowers
+ * refaultObject now correctly refaults the object.
+ *
+ * Revision 1.65 2001/12/20 18:56:15 mpowers
+ * Refinements to snapshotting and calling processRecentChanges.
+ *
+ * Revision 1.64 2001/12/10 15:11:41 mpowers
+ * Now only tracking a commit snapshot after an object has been modified.
+ *
+ * Revision 1.63 2001/11/14 00:08:10 mpowers
+ * Now marking context changed when objects are inserted or deleted
+ * and when child contexts save their changes into this context.
+ *
+ * Revision 1.62 2001/11/07 14:49:31 mpowers
+ * invalidateAllObjects now handles objects manually discarded in the course
+ * of invalidation.
+ *
+ * Revision 1.61 2001/10/26 20:02:49 mpowers
+ * No longer using NSNotificationQueue: all notifications are posted immed.
+ *
+ * Revision 1.60 2001/10/26 18:37:50 mpowers
+ * Now using NSRunLoop to correctly flush recent changes before delayed
+ * observers and AWT events.
+ *
+ * Revision 1.59 2001/10/23 22:29:59 mpowers
+ * Now posting notifications immediately.
+ * Recent changes are flushed at ObserverPrioritySixth, soon to change.
+ *
+ * Revision 1.58 2001/09/10 14:16:51 mpowers
+ * EditingContexts now relay InvalidatedAllObjectsInStore notifications.
+ *
+ * Revision 1.57 2001/06/18 14:11:15 mpowers
+ * Inserting a deleted object simply cancels the delete operation.
+ *
+ * Revision 1.56 2001/06/07 22:07:59 mpowers
+ * Now handling delete notifications before update notifications.
+ * Eliminated the case where deleted objects were also being put
+ * on the updated list when notifying child contexts.
+ *
+ * Revision 1.55 2001/05/18 21:04:33 mpowers
+ * Reimplemented EditingContext.initializeObject.
+ *
+ * Revision 1.54 2001/05/05 23:05:42 mpowers
+ * Implemented Array Faults.
+ *
+ * Revision 1.53 2001/05/05 15:00:06 mpowers
+ * Tested load-on-demand: still works.
+ * Now using registerClone for consistency.
+ * Editing context is temporarily posting notification on objectWillChange.
+ *
+ * Revision 1.52 2001/05/05 14:11:48 mpowers
+ * Implemented registerClone.
+ *
+ * Revision 1.51 2001/05/04 16:57:56 mpowers
+ * Now correctly transposing references to editing contexts when
+ * cloning/copying between editing contexts.
+ *
+ * Revision 1.50 2001/05/02 17:58:41 mpowers
+ * Removed debugging code, added comments.
+ *
+ * Revision 1.49 2001/05/02 15:47:40 mpowers
+ * Fixed the pernicious problem with reverts: recordObject was recording
+ * a snapshot of the clone before the transposition-copy happened,
+ * so the revert object would lose all of its transposed relationships.
+ *
+ * Revision 1.48 2001/05/02 12:39:05 mpowers
+ * Fixed a nasty problem with transpose-cloning and faultForGlobalID.
+ * Now we're forced to create a deep clone, registered it, and then
+ * transpose it after it has been registered.
+ *
+ * Revision 1.47 2001/04/30 13:15:24 mpowers
+ * Child contexts re-initializing objects invalidated in parent now
+ * propery transpose relationships.
+ *
+ * Revision 1.46 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.45 2001/04/29 02:29:31 mpowers
+ * Debugging relationship faulting.
+ *
+ * Revision 1.44 2001/04/28 16:18:44 mpowers
+ * Implementing relationships.
+ *
+ * Revision 1.43 2001/04/28 14:12:23 mpowers
+ * Refactored cloning/copying into KeyValueCodingUtilities.
+ *
+ * Revision 1.42 2001/04/27 00:27:11 mpowers
+ * Provided description to not-implemented exceptions.
+ *
+ * Revision 1.41 2001/04/26 01:16:44 mpowers
+ * Major bug fix so we no longer accumulate objects in the all objects
+ * list with every InvalidateAllObjectsInStore.
+ *
+ * Revision 1.40 2001/04/21 23:07:49 mpowers
+ * Now only broadcasts notifications if there's actually a change.
+ *
+ * Revision 1.39 2001/04/13 16:33:11 mpowers
+ * Corrected the refaulting behavior.
+ *
+ * Revision 1.38 2001/04/09 21:42:10 mpowers
+ * Debugging and optimizing notifications.
+ *
+ * Revision 1.37 2001/04/08 20:59:47 mpowers
+ * objectsForFetchSpecification now relies on faultForGlobalID.
+ *
+ * Revision 1.36 2001/04/03 20:36:01 mpowers
+ * Fixed refaulting/reverting/invalidating to be self-consistent.
+ *
+ * Revision 1.35 2001/03/29 03:29:49 mpowers
+ * Now using KeyValueCoding and Support instead of Introspector.
+ *
+ * Revision 1.34 2001/03/28 14:06:29 mpowers
+ * Implemented snapshots. Revert now uses snapshots.
+ *
+ * Revision 1.33 2001/03/20 23:20:33 mpowers
+ * invalidating all objects now sets the dirty flag to false.
+ *
+ * Revision 1.32 2001/03/19 21:44:36 mpowers
+ * Reverts reinitialize for now.
+ * Testing for inserted objects instead of temp id when invalidating object.
+ *
+ * Revision 1.31 2001/03/15 21:10:26 mpowers
+ * Implemented global id re-registration for newly saved inserts.
+ *
+ * Revision 1.30 2001/03/13 21:41:34 mpowers
+ * Broadcasting willChange for any change to hasChanges.
+ * Fixed major bug with inserted objects treated as updated objects
+ * in child display groups.
+ *
+ * Revision 1.29 2001/03/09 22:10:30 mpowers
+ * Fine tuned initializeObject.
+ *
+ * Revision 1.28 2001/03/06 23:23:55 mpowers
+ * objectForGlobalID now returns null if not found.
+ * objectsForFetchSpecification again does things the old way, for now.
+ *
+ * Revision 1.27 2001/03/02 16:31:45 mpowers
+ * Trying to better handle fetches from child contexts.
+ * No longer trying to invalidate temporary objects.
+ *
+ * Revision 1.26 2001/02/28 16:25:19 mpowers
+ * Now calling globalIDForObject internally.
+ *
+ * Revision 1.25 2001/02/27 17:36:55 mpowers
+ * Objects inserted from child now preserve the existing temporary id.
+ *
+ * Revision 1.24 2001/02/27 02:11:17 mpowers
+ * Now throwing exception when cloning fails.
+ * Removed debugging printlns.
+ *
+ * Revision 1.23 2001/02/26 22:41:51 mpowers
+ * Implemented null placeholder classes.
+ * Duplicator now uses NSNull.
+ * No longer catching base exception class.
+ *
+ * Revision 1.22 2001/02/26 21:18:45 mpowers
+ * Now marking edited objects from child contexts that were not already
+ * recorded in parent as changed in saveChangesInEditingContext.
+ *
+ * Revision 1.21 2001/02/26 15:53:22 mpowers
+ * Fine-tuning notification firing.
+ * Child display groups now update properly after parent save or invalidate.
+ *
+ * Revision 1.20 2001/02/24 17:03:22 mpowers
+ * Implemented the notification queue, and changed editing context to use it.
+ *
+ * Revision 1.19 2001/02/23 23:44:15 mpowers
+ * Fine-tuning notification handling.
+ *
+ * Revision 1.18 2001/02/22 22:56:57 mpowers
+ * Only refaulting edited objects on parent store invalidateAll.
+ *
+ * Revision 1.17 2001/02/22 20:54:39 mpowers
+ * Implemented notification handling.
+ *
+ * Revision 1.16 2001/02/21 22:10:55 mpowers
+ * Editing context is now posting appropriate notifications.
+ *
+ * Revision 1.15 2001/02/21 21:17:32 mpowers
+ * Now retaining a reference to the recent changes observer.
+ * Better documented need to retain reference.
+ * Started implementing notifications.
+ *
+ * Revision 1.14 2001/02/20 17:24:22 mpowers
+ * Now using reference keys in objectToID.
+ *
+ * Revision 1.13 2001/02/20 16:45:36 mpowers
+ * Child data sources now accept a data source instead of an editing context
+ * for more flexibility. Child data sources now forward relationship
+ * methods to parent source.
+ *
+ * Revision 1.12 2001/02/16 22:51:29 mpowers
+ * Now deep-cloning objects passed between editing contexts.
+ *
+ * Revision 1.11 2001/02/16 18:34:19 mpowers
+ * Implementing nested contexts.
+ *
+ * Revision 1.9 2001/02/15 21:13:30 mpowers
+ * First draft implementation is complete. Now on to debugging.
+ *
+ * Revision 1.7 2001/02/13 23:24:29 mpowers
+ * Implementing more of editing context.
+ *
+ * Revision 1.4 2001/02/09 22:09:34 mpowers
+ * Completed implementation of EOObjectStore.
+ *
+ * Revision 1.3 2001/02/06 15:24:11 mpowers
+ * Widened parameters on abstract method to fix build.
+ *
+ * Revision 1.2 2001/02/05 03:45:37 mpowers
+ * Starting work on EOEditingContext.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers
+ * Contributing wotonomy.
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java
new file mode 100644
index 0000000..112531c
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java
@@ -0,0 +1,219 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* EOEnterpriseObject defines the required methods a data object
+* must implement to take full advantage of the control package.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public interface EOEnterpriseObject
+ extends EOKeyValueCodingAdditions,
+ EORelationshipManipulation,
+ EODeferredFaulting,
+ EOValidation
+{
+ /**
+ * Returns a List of all property keys defined on this object.
+ * This includes both attributes and relationships.
+ */
+ NSArray allPropertyKeys();
+
+ /**
+ * Returns a list of all attributes defined on this object.
+ * Attributes are all properties that are not relationships.
+ */
+ NSArray attributeKeys();
+
+ //void awakeFromClientUpdate(EOEditingContext aContext)
+
+ /**
+ * Called when the object has first been fetched into the
+ * specified editing context.
+ */
+ void awakeFromFetch(EOEditingContext anEditingContext);
+
+ /**
+ * Called when the object has been inserted into the
+ * specified editing context.
+ */
+ void awakeFromInsertion(EOEditingContext anEditingContext);
+
+ /**
+ * Returns a Map representing the delta of the current state
+ * from the state represented in the specified snapshot.
+ * The result will contain only the keys that have changed
+ * and their values. Relationship keys will map to an NSArray
+ * that contains an NSArray of added objects and an NSArray
+ * of removed objects, in that order.
+ */
+ NSDictionary changesFromSnapshot(NSDictionary snapshot);
+
+ /**
+ * Returns a class description for this object.
+ */
+ EOClassDescription classDescription();
+
+ /**
+ * Returns a class description for the object at the
+ * other end of the specified relationship key.
+ */
+ EOClassDescription classDescriptionForDestinationKey(String aKey);
+
+ /**
+ * Clears all property values for this object.
+ * This method is called to clean-up an object that
+ * will no longer be used, and implementations should
+ * ensure that all references are set to null to
+ * prevent problems with garbage-collection.
+ */
+ void clearProperties();
+
+ /**
+ * Returns the delete rule constant defined on EOClassDescription
+ * for the relationship defined by the specified key.
+ */
+ int deleteRuleForRelationshipKey(String aRelationshipKey);
+
+ /**
+ * Returns the editing context in which this object is registered.
+ */
+ EOEditingContext editingContext();
+
+ /**
+ * Returns the name of the entity that this object represents.
+ */
+ String entityName();
+
+ /**
+ * Returns a String containing all property keys and values for
+ * this object. Relationships should be represented by calling
+ * eoShallowDescription() on the object.
+ */
+ String eoDescription();
+
+ /**
+ * Returns a String containing all attribute keys and values for
+ * this object. Relationships are not included.
+ */
+ String eoShallowDescription();
+
+ /**
+ * Returns the key used to reference this object on the
+ * object at the other end of the specified relationship.
+ */
+ String inverseForRelationshipKey(String aRelationshipKey);
+
+ //Object invokeRemoteMethod(
+ // String aMethodName, Class[] aTypeArray Object[] anArgumentArray)
+
+ /**
+ * Returns whether the specified relationship key represents
+ * a to-many relationship.
+ */
+ boolean isToManyKey(String aKey);
+
+ /**
+ * Returns whether the objects at the other end of the specified
+ * relationship should be deleted when this object is deleted.
+ */
+ boolean ownsDestinationObjectsForRelationshipKey(String aKey);
+
+ //void prepareValuesForClient()
+
+ /**
+ * Called to perform the delete propagation for this object
+ * on the specified editing context. All relationships
+ * should be processed according to their corresponding
+ * delete rule.
+ */
+ void propagateDeleteWithEditingContext(EOEditingContext aContext);
+
+ /**
+ * Applies the changes from the specified snapshot to
+ * this object.
+ * @see #changesFromSnapshot(NSDictionary)
+ */
+ void reapplyChangesFromDictionary(NSDictionary aDeltaSnapshot);
+
+ /**
+ * Returns a snapshot of the current state of this object.
+ * All property keys are mapped to their values; nulls are
+ * represented by NSNull.
+ */
+ NSDictionary snapshot();
+
+ /**
+ * Returns a List of the to-many relationship keys
+ * for this object.
+ */
+ NSArray toManyRelationshipKeys();
+
+ /**
+ * Returns a List of the to-one relationship keys
+ * for this object.
+ */
+ NSArray toOneRelationshipKeys();
+
+ /**
+ * Applies the specified snapshot to this object,
+ * converting NSNulls to null and calling
+ * takeStoredValueForKey for each key in the Map.
+ */
+ void updateFromSnapshot(NSDictionary aSnapshot);
+
+ /**
+ * Returns a short, stateful string representation
+ * of this object.
+ */
+ String userPresentableDescription();
+
+ /**
+ * This method should be implemented to call
+ * EOObserverCenter.objectWillChange( this ),
+ * and it should be called by each setter method
+ * on this object before changes are made to the
+ * object's internal state.
+ */
+ void willChange();
+}
+
+/*
+ * $Log$
+ * 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.1 2001/11/13 04:13:59 mpowers
+ * Added interfaces needed to begin work on EOCustomObject.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java
new file mode 100644
index 0000000..19dc2df
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java
@@ -0,0 +1,102 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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;
+
+/**
+* EOFaultHandler defines the contract for objects that can
+* create and populate faults. In wotonomy, this interface is
+* currently only a marker interface.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public abstract class EOFaultHandler {
+
+ protected Class _targetClass;
+
+ public EOFaultHandler() {
+ super();
+ }
+
+ public static EOFaultHandler handlerForFault(Object obj) {
+ if (!(obj instanceof EOFaulting))
+ throw new IllegalArgumentException("Object must implement EOFaulting");
+ return ((EOFaulting)obj).faultHandler();
+ }
+
+ public static boolean isFault(Object obj) {
+ if (obj == null)
+ return false;
+ boolean isit = (obj instanceof EOFaulting);
+ if (isit)
+ isit = ((EOFaulting)obj).isFault();
+ return isit;
+ }
+
+ public static void makeObjectIntoFault(Object obj, EOFaultHandler handler) {
+ if (!(obj instanceof EOFaulting))
+ throw new IllegalArgumentException("Object must implement EOFaulting");
+ ((EOFaulting)obj).turnIntoFault(handler);
+ }
+
+ public static void clearFault(Object obj) {
+ if (!(obj instanceof EOFaulting))
+ throw new IllegalArgumentException("Object must implement EOFaulting");
+ ((EOFaulting)obj).clearFault();
+ }
+
+ public Class targetClass() {
+ return _targetClass;
+ }
+
+ public Object createFaultForDeferredFault(Object fault, EOEnterpriseObject source) {
+ return fault;
+ }
+
+ public String descriptionForObject(Object obj) {
+ if (obj == null)
+ return "<null>";
+ return obj.toString();
+ }
+
+ public String eoShallowDescription(Object obj) {
+ return null;
+ }
+
+ public abstract void completeInitializationOfObject(Object obj);
+
+ public abstract void faultWillFire(Object obj);
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.2 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.1 2001/11/13 04:13:59 mpowers
+ * Added interfaces needed to begin work on EOCustomObject.
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java
new file mode 100644
index 0000000..95b3e35
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java
@@ -0,0 +1,79 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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;
+
+/**
+* EOFaulting defines the requirements for objects that
+* can be faulted by an EOFaultManager.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EOFaulting
+{
+ /**
+ * Called by EOFaultHandler to prepare the object to be turned into a fault.
+ */
+ void clearFault();
+
+ /**
+ * Returns this object's EOFaultHandler.
+ */
+ EOFaultHandler faultHandler();
+
+ /**
+ * Returns whether this object is currently a fault.
+ * Returns true if this object has not yet retrieved any values.
+ */
+ boolean isFault();
+
+ /**
+ * Turns this object into a fault using the specified fault handler.
+ */
+ void turnIntoFault( EOFaultHandler aFaultHandler );
+
+ /**
+ * Called to completely fire the fault, reading all values.
+ * This method may be implemented to call willRead(null).
+ */
+ void willRead();
+
+ /**
+ * Called to fire the fault for the specified key.
+ * The fault manager is required to populate the specified key
+ * with a value, and may populate any or all of the other values
+ * on this object. A null key will populate all values on the object.
+ * NOTE: This method is not part of the specification.
+ */
+ void willRead( String aKey );
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2001/11/13 04:13:59 mpowers
+ * Added interfaces needed to begin work on EOCustomObject.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java
new file mode 100644
index 0000000..71f3b78
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java
@@ -0,0 +1,565 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOFetchSpecification defines the parameters used to request
+* objects from an EOObjectStore. They are commonly created
+* and passed to a EODataSource which fetches from its
+* EOEditingContext, which passes the call up to its root
+* EOObjectStore's objectsWithFetchSpecification method.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOFetchSpecification implements EOKeyValueArchiving {
+ private boolean fetchesRawRows;
+ private String entityName;
+ private NSDictionary hints;
+ private boolean deep;
+ private int fetchLimit;
+ private boolean locksObjects;
+ private NSArray prefetchingRelationshipKeyPaths;
+ private boolean promptsAfterFetchLimit;
+ private EOQualifier qualifier;
+ private NSArray rawRowKeyPaths;
+ private boolean refreshesRefetchedObjects;
+ private boolean requiresAllQualifierBindingVariables;
+ private NSArray sortOrderings;
+ private boolean distinct;
+
+ /**
+ * Default constructor initializes internal state.
+ */
+ public EOFetchSpecification()
+ {
+ fetchesRawRows = false;
+ entityName = null;
+ hints = null;
+ deep = true;
+ fetchLimit = 0;
+ locksObjects = false;
+ prefetchingRelationshipKeyPaths = null;
+ promptsAfterFetchLimit = false;
+ qualifier = null;
+ rawRowKeyPaths = null;
+ refreshesRefetchedObjects = false;
+ requiresAllQualifierBindingVariables = false;
+ sortOrderings = null;
+ distinct = false;
+ }
+
+ /**
+ * Constructs a fetch specification for the specified entity type using
+ * the specified qualifier and sort ordering.
+ */
+ public EOFetchSpecification( String anEntityName, EOQualifier aQualifier, List aSortOrderingList )
+ {
+ this();
+ entityName = anEntityName;
+ qualifier = aQualifier;
+ sortOrderings = new NSArray( (Collection) aSortOrderingList );
+ }
+
+ /**
+ * Constructs a fetch specification for the specified entity type using
+ * the specified qualifier and sort ordering, distinct flag, deep flag,
+ * and hints dictionary.
+ */
+ public EOFetchSpecification( String anEntityName, EOQualifier aQualifier, NSArray aSortOrderingList,
+ boolean usesDistinct, boolean isDeep, Map aHintMap)
+ {
+ this();
+ entityName = anEntityName;
+ qualifier = aQualifier;
+ sortOrderings = new NSArray( (Collection) aSortOrderingList );
+ distinct = usesDistinct;
+ deep = isDeep;
+ hints = new NSMutableDictionary( (Map) aHintMap );
+ }
+
+ /**
+ * Convenience to return the named fetch specification from the class description
+ * corresponding to the specified entity name. Returns null if either entityName
+ * or spec name cannot be resolved.
+ */
+ public static EOFetchSpecification fetchSpecificationNamed( String name, String entityName)
+ {
+ EOClassDescription classDesc = EOClassDescription.classDescriptionForEntityName( entityName );
+ if ( classDesc == null ) return null;
+ return classDesc.fetchSpecificationNamed( name );
+ }
+
+ /**
+ * Implemented to return a new fetch specification that is a deep copy of this one.
+ */
+ public Object clone()
+ {
+ EOFetchSpecification clone = new EOFetchSpecification();
+
+ clone.fetchesRawRows = this.fetchesRawRows;
+ clone.entityName = this.entityName;
+ if ( this.hints != null )
+ clone.hints = new NSDictionary( (Map) this.hints );
+ clone.deep = this.deep;
+ clone.locksObjects = this.locksObjects;
+ if ( this.prefetchingRelationshipKeyPaths != null )
+ clone.prefetchingRelationshipKeyPaths =
+ new NSArray( (List) prefetchingRelationshipKeyPaths );
+ clone.promptsAfterFetchLimit = this.promptsAfterFetchLimit;
+ if ( this.qualifier != null )
+ clone.qualifier = this.qualifier; //FIXME: probably should clone?
+ if ( this.rawRowKeyPaths != null )
+ clone.rawRowKeyPaths = new NSArray( (List) this.rawRowKeyPaths );
+ clone.refreshesRefetchedObjects = this.refreshesRefetchedObjects;
+ clone.requiresAllQualifierBindingVariables =
+ this.requiresAllQualifierBindingVariables;
+ if ( this.sortOrderings != null )
+ clone.sortOrderings = new NSArray( (List) this.sortOrderings );
+ clone.distinct = this.distinct;
+
+ return clone;
+ }
+
+ /**
+ * Returns the name of the entity fetched by this fetch spec.
+ */
+ public String entityName()
+ {
+ return entityName;
+ }
+
+ /**
+ * Returns the current fetch limit.
+ * A fetch limit of zero indicates no fetch limit.
+ * Zero is the default.
+ */
+ public int fetchLimit()
+ {
+ return fetchLimit;
+ }
+
+ /**
+ * Returns whether this fetch spec will fetch raw rows.
+ * Default is false.
+ */
+ public boolean fetchesRawRows()
+ {
+ return fetchesRawRows;
+ }
+
+ /**
+ * Returns a fetch specification that resolves the bindings
+ * in the specified map.
+ */
+ public EOFetchSpecification
+ fetchSpecificationWithQualifierBindings(Map aBindingMap)
+ {
+ throw new WotonomyException( "Not implemented yet" );
+ }
+
+ /**
+ * Returns a Map containing the hints used by this fetch specification,
+ * or null if no hints have been specified.
+ */
+ public NSDictionary hints()
+ {
+ if ( hints == null ) return null;
+ return new NSDictionary( (NSDictionary) hints );
+ }
+
+ /**
+ * Returns whether entities related to the primary
+ * entities are fetched by this fetch spec. If true, all relationships
+ * whose destinations meet the qualifier criteria will be returned
+ * in addition to primary results. If false, only the primary entities
+ * will be returned. Default is true.
+ */
+ public boolean isDeep()
+ {
+ return deep;
+ }
+
+ /**
+ * Returns whether this data source should lock objects that
+ * are fetched. Default is false.
+ */
+ public boolean locksObjects()
+ {
+ return locksObjects;
+ }
+
+ /***
+ * Returns a List of relationships for the fetched objects that
+ * should also be fetched, or null if no such list has been specified.
+ * Use this to avoid additional calls to the server to fetch
+ * relationships that you know you will use.
+ * NOTE: wotonomy allows you to specify non-relational keys
+ * as well.
+ */
+ public NSArray prefetchingRelationshipKeyPaths()
+ {
+ return prefetchingRelationshipKeyPaths;
+ }
+
+ /**
+ * Returns whether the user should be prompted to continue
+ * when the fetch limit has been exceeded.
+ * Default is false.
+ */
+ public boolean promptsAfterFetchLimit()
+ {
+ return promptsAfterFetchLimit;
+ }
+
+ /**
+ * Returns the qualifier used by this fetch specification,
+ * or null if none has been specified.
+ */
+ public EOQualifier qualifier()
+ {
+ return qualifier;
+ }
+
+ /**
+ * Returns a List of keys or key paths for which
+ * values should be returned when fetching raw rows,
+ * or null if no raw row key paths have been specified.
+ */
+ public NSArray rawRowKeyPaths()
+ {
+ return rawRowKeyPaths;
+ }
+
+ /**
+ * Returns whether fetched objects should replace
+ * modified versions already fetched into an editing context.
+ * If true, those changes will be lost.
+ * Default is false.
+ */
+ public boolean refreshesRefetchedObjects()
+ {
+ return refreshesRefetchedObjects;
+ }
+
+ /**
+ * Returns whether all qualifier bindings must be specified
+ * in order to fetch. If true, an exception is thrown if
+ * unspecified bindings exist. If false, unspecified bindings
+ * will be removed from the qualifier. Default is false.
+ */
+ public boolean requiresAllQualifierBindingVariables()
+ {
+ return requiresAllQualifierBindingVariables;
+ }
+
+ /**
+ * Sets the name of the entity fetched by this spec.
+ */
+ public void setEntityName(String aName)
+ {
+ entityName = aName;
+ }
+
+ /**
+ * Sets whether this fetch spec will return raw rows.
+ */
+ public void setFetchesRawRows(boolean shouldFetchRawRows)
+ {
+ fetchesRawRows = shouldFetchRawRows;
+ }
+
+ /**
+ * Sets the limit on the number of records returned for this fetch spec.
+ * Zero indicates no limit on fetches.
+ */
+ public void setFetchLimit(int aLimit)
+ {
+ fetchLimit = aLimit;
+ }
+
+ /**
+ * Sets the hints passed by this fetch spec.
+ */
+ public void setHints(Map aHintMap)
+ {
+ if ( aHintMap == null )
+ {
+ hints = null;
+ }
+ else
+ {
+ hints = new NSDictionary( (Map) aHintMap );
+ }
+ }
+
+ /**
+ * Sets whether this fetch specification fetches deeply.
+ */
+ public void setIsDeep(boolean isDeep)
+ {
+ deep = isDeep;
+ }
+
+ /**
+ * Sets whether this fetch spec locks objects that
+ * are returned by the fetch.
+ */
+ public void setLocksObjects(boolean shouldLockObjects)
+ {
+ locksObjects = shouldLockObjects;
+ }
+
+ /**
+ * Sets the prefetch key paths that should be used as an optimization
+ * hint to the server. NOTE: wotonomy allows you to specify non-relationship
+ * keys as well.
+ */
+ public void setPrefetchingRelationshipKeyPaths(List aKeyPathList)
+ {
+ if ( aKeyPathList == null )
+ {
+ prefetchingRelationshipKeyPaths = null;
+ }
+ else
+ {
+ prefetchingRelationshipKeyPaths = new NSArray( (List) aKeyPathList );
+ }
+ }
+
+ /**
+ * Sets whether the user should be prompted when the fetch limit has been
+ * reached.
+ */
+ public void setPromptsAfterFetchLimit(boolean shouldPrompt)
+ {
+ promptsAfterFetchLimit = shouldPrompt;
+ }
+
+ /**
+ * Sets the qualifier used by this fetch specification.
+ */
+ public void setQualifier(EOQualifier aQualifier)
+ {
+ qualifier = aQualifier;
+ }
+
+ /**
+ * Sets the key paths to be returned if this fetch spec
+ * is returning raw rows.
+ */
+ public void setRawRowKeyPaths(List aKeyPathList)
+ {
+ if ( aKeyPathList == null )
+ {
+ rawRowKeyPaths = null;
+ }
+ else
+ {
+ rawRowKeyPaths = new NSArray( (List) aKeyPathList );
+ }
+ }
+
+ /**
+ * Sets whether modified objects in an editing context should
+ * be replaced by newer versions returned by this fetch spec.
+ */
+ public void setRefreshesRefetchedObjects(boolean shouldRefresh)
+ {
+ refreshesRefetchedObjects = shouldRefresh;
+ }
+
+ /**
+ * Sets whether this fetch spec should require all bindings to be
+ * resolved before executing.
+ */
+ public void setRequiresAllQualifierBindingVariables(boolean shouldRequireAll)
+ {
+ requiresAllQualifierBindingVariables = shouldRequireAll;
+ }
+
+ /**
+ * Sets the sort orderings used by this fetch spec.
+ */
+ public void setSortOrderings(List aSortList)
+ {
+ if ( aSortList == null )
+ {
+ sortOrderings = null;
+ }
+ else
+ {
+ sortOrderings = new NSArray( (List) aSortList );
+ }
+ }
+
+ /**
+ * Sets whether this fetch spec should return only distinct
+ * objects.
+ */
+ public void setUsesDistinct(boolean shouldUseDistinct)
+ {
+ distinct = shouldUseDistinct;
+ }
+
+ /**
+ * Returns a List of the sort orderings used by this fetch spec,
+ * or null if none have been specified.
+ */
+ public NSArray sortOrderings()
+ {
+ return sortOrderings;
+ }
+
+ /**
+ * Returns a string representation of this fetch specification.
+ */
+ public String toString()
+ {
+ return "[FetchSpecification:qualifier=("+qualifier+"),sortOrderings="+sortOrderings+"]";
+ }
+
+ /**
+ * Returns whether this fetch specification will return only one
+ * reference to each distinct object returned by the fetch.
+ * Default is false.
+ */
+ public boolean usesDistinct()
+ {
+ return distinct;
+ }
+
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EOFetchSpecification", "class");
+ arch.encodeObject(entityName(), "entityName");
+ arch.encodeInt(fetchLimit(), "fetchLimit");
+
+ //flags
+ if (isDeep())
+ arch.encodeObject("YES", "isDeep");
+ arch.encodeObject(qualifier(), "qualifier");
+ if (refreshesRefetchedObjects())
+ arch.encodeObject("YES", "refreshesRefetchedObjects");
+ if (locksObjects())
+ arch.encodeObject("YES", "locksObjects");
+ if (fetchesRawRows())
+ arch.encodeObject("YES", "fetchesRawRows");
+ if (promptsAfterFetchLimit())
+ arch.encodeObject("YES", "promptsAfterFetchLimit");
+ if (requiresAllQualifierBindingVariables())
+ arch.encodeObject("YES", "requiresAllQualifierBindingVariables");
+ if (usesDistinct())
+ arch.encodeObject("YES", "usesDistinct");
+
+ //encode arrays
+ if (sortOrderings() != null) {
+ NSMutableArray arr = new NSMutableArray(sortOrderings().count());
+ for (int i = 0; i < sortOrderings.count(); i++) {
+ EOSortOrdering so = (EOSortOrdering)sortOrderings().objectAtIndex(i);
+ EOKeyValueArchiver ar2 = new EOKeyValueArchiver();
+ so.encodeWithKeyValueArchiver(ar2);
+ arr.addObject(ar2.dictionary());
+ }
+ arch.encodeObject(arr, "sortOrderings");
+ }
+ if (rawRowKeyPaths != null && rawRowKeyPaths.count() > 0)
+ arch.encodeObject(rawRowKeyPaths, "rawRowKeyPaths");
+ if (prefetchingRelationshipKeyPaths != null && prefetchingRelationshipKeyPaths.count() > 0)
+ arch.encodeObject(rawRowKeyPaths, "prefetchingRelationshipKeyPaths");
+ if (hints != null && hints.count() > 0)
+ arch.encodeObject(hints, "hints");
+ }
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver unarch) {
+ EOFetchSpecification fs = new EOFetchSpecification();
+ fs.setEntityName((String)unarch.decodeObjectForKey("entityName"));
+ fs.setFetchLimit(unarch.decodeIntForKey("fetchLimit"));
+ fs.setIsDeep(unarch.decodeBoolForKey("isDeep"));
+ fs.setRefreshesRefetchedObjects(unarch.decodeBoolForKey("refreshesRefetchedObjects"));
+
+ //Sort orderings
+ NSArray arr = (NSArray)unarch.decodeObjectForKey("sortOrderings");
+ if (arr != null && arr.count() > 0) {
+ NSMutableArray orderings = new NSMutableArray(arr.count());
+ for (int i = 0; i < arr.count(); i++) {
+ NSDictionary so = (NSDictionary)arr.objectAtIndex(i);
+ String selname = (String)so.objectForKey("selectorName");
+ NSSelector selector = EOSortOrdering.CompareAscending;
+ if (selname.startsWith("compareDescending"))
+ selector = EOSortOrdering.CompareDescending;
+ else if (selname.startsWith("compareCaseInsensitiveAscending"))
+ selector = EOSortOrdering.CompareCaseInsensitiveAscending;
+ else if (selname.startsWith("compareCaseInsensitiveDescending"))
+ selector = EOSortOrdering.CompareCaseInsensitiveDescending;
+ EOSortOrdering eoso = new EOSortOrdering((String)so.objectForKey("key"), selector);
+ orderings.addObject(eoso);
+ }
+ fs.setSortOrderings(orderings);
+ }
+ //raw rows
+ arr = (NSArray)unarch.decodeObjectForKey("rawRowKeyPaths");
+ if (arr != null && arr.count() > 0) {
+ fs.setFetchesRawRows(true);
+ fs.setRawRowKeyPaths(arr);
+ }
+ //qualifier
+ fs.setQualifier((EOQualifier)unarch.decodeObjectForKey("qualifier"));
+ return fs;
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.4 2003/08/11 18:19:01 chochos
+ * encoding/decoding with EOKeyValueArchiving now works properly
+ *
+ * Revision 1.3 2003/08/09 01:22:20 chochos
+ * implements EOKeyValueArchiving (and unarchiving)
+ *
+ * Revision 1.2 2001/11/24 17:32:57 mpowers
+ * We now have a real implementation.
+ *
+ * Revision 1.1 2001/02/05 03:45:37 mpowers
+ * Starting work on EOEditingContext.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java
new file mode 100644
index 0000000..4358ecf
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java
@@ -0,0 +1,151 @@
+/*
+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 net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNull;
+
+/**
+* EOGenericRecord extends EOCustomObject to provide a
+* general-purpose implementation, relying entirely on the
+* class description for entity-specific behavior, and
+* storing data in a dictionary.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOGenericRecord extends EOCustomObject
+{
+ private EOClassDescription classDescription;
+ private NSMutableDictionary dataDictionary;
+
+ /**
+ * Default constructor.
+ */
+ protected EOGenericRecord()
+ {
+ classDescription = null;
+ dataDictionary = new NSMutableDictionary();
+ }
+
+ /**
+ * Preferred constructor.
+ */
+ public EOGenericRecord( EOClassDescription aDescription )
+ {
+ this();
+ classDescription = aDescription;
+ }
+
+ /**
+ * Overridden to return true so that deferred faults are used.
+ */
+ public static boolean usesDeferredFaultCreation()
+ {
+ return true;
+ }
+
+ /**
+ * Compatibility constructor: aContext and anID are ignored.
+ */
+ public EOGenericRecord(
+ EOEditingContext aContext, EOClassDescription aDescription, EOGlobalID anID )
+ {
+ this( aDescription );
+ }
+
+ /**
+ * Returns a class description for this object.
+ * Overridden to return the class description passed
+ * into the constructor.
+ */
+ public EOClassDescription classDescription()
+ {
+ return classDescription;
+ }
+
+ // interface EOKeyValueCoding
+
+ /**
+ * Calls storedValueForKey.
+ */
+ public Object valueForKey( String aKey )
+ {
+ return storedValueForKey( aKey );
+ }
+
+ /**
+ * Calls willChange and then calls takeStoredValueForKey.
+ */
+ public void takeValueForKey( Object aValue, String aKey )
+ {
+ willChange();
+ takeStoredValueForKey( aValue, aKey );
+ }
+
+ /**
+ * Calls willRead for the specified key,
+ * and returns the corresponding value from
+ * the data dictionary. Keys that do not
+ * exist will return null.
+ */
+ public Object storedValueForKey( String aKey )
+ {
+ willRead( aKey );
+ Object result = dataDictionary.objectForKey( aKey );
+ if ( NSNull.nullValue().equals( result ) ) result = null;
+ return result;
+ }
+
+ /**
+ * Writes the specified value into the data dictionary
+ * for the specified key. Nulls are stored as NSNulls
+ * in the dictionary.
+ * No checking is performed to determine whether the
+ * key is a valid attribute key.
+ */
+ public void takeStoredValueForKey( Object aValue, String aKey )
+ {
+ if ( aValue == null ) aValue = NSNull.nullValue();
+ dataDictionary.setObjectForKey( aValue, aKey );
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.2 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.1 2001/11/18 18:57:10 mpowers
+ * Implemented EOGenericRecord.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java
new file mode 100644
index 0000000..1b4ccbe
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java
@@ -0,0 +1,83 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.io.Serializable;
+
+/**
+* A pure java implementation of EOGlobalID.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public abstract class EOGlobalID implements Cloneable, Serializable
+{
+ /**
+ * ObjectStores broadcast this notification when they
+ * replace a temporary global id with a permanent one.
+ * EditingContexts listen for these notifications to
+ * update their mapping of global ids to objects.
+ * The object of the notification is null, and the user
+ * info contains a mapping of the old temporary ids to
+ * the new permanent ids.
+ */
+ public static final String GlobalIDChangedNotification
+ = "GlobalIDChangedNotification";
+
+ /**
+ * Returns whether this id is a temporary id.
+ * Temporary ids are generated for newly created
+ * objects that have not been persisted. When
+ * persisted, the temporary id is discarded in favor
+ * of the one generated by the object store.
+ */
+ public abstract boolean isTemporary();
+
+ /**
+ * Returns a copy of this object.
+ * This implementation calls super.clone().
+ */
+ public Object clone() throws CloneNotSupportedException
+ {
+ return super.clone();
+ }
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.4 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.3 2001/03/15 21:10:26 mpowers
+ * Implemented global id re-registration for newly saved inserts.
+ *
+ * Revision 1.2 2001/02/15 21:13:30 mpowers
+ * First draft implementation is complete. Now on to debugging.
+ *
+ * Revision 1.1 2001/02/05 03:45:37 mpowers
+ * Starting work on EOEditingContext.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java
new file mode 100644
index 0000000..ee7aac1
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java
@@ -0,0 +1,72 @@
+/*
+ Wotonomy: OpenStep design patterns for pure Java applications.
+ Copyright (C) 2001 Michael Powers
+
+ 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;
+
+/**
+*
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public class EOIntegralKeyGlobalID extends EOKeyGlobalID {
+
+ protected Number keyValue;
+
+ public EOIntegralKeyGlobalID(String entityName, Number value) {
+ super(entityName, 0);
+ keyValue = value;
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyGlobalID#keyValues()
+ */
+ public Object[] keyValues() {
+ return new Object[]{ keyValue };
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyGlobalID#_keyValuesNoCopy()
+ */
+ public Object[] _keyValuesNoCopy() {
+ return new Object[]{ keyValue };
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyGlobalID#keyCount()
+ */
+ public int keyCount() {
+ return 1;
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOGlobalID#isTemporary()
+ */
+ public boolean isTemporary() {
+ return false;
+ }
+
+}
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.2 2003/08/19 01:59:01 chochos
+ * Added the wotonomy headers
+ *
+ */
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java
new file mode 100644
index 0000000..97baaab
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java
@@ -0,0 +1,151 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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 net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOKeyComparisonQualifier
+ extends EOQualifier
+ implements EOKeyValueArchiving, EOQualifierEvaluation {
+
+ private String _leftKey;
+ private String _rightKey;
+ private NSSelector _selector;
+
+ public EOKeyComparisonQualifier(String leftKey, NSSelector selector, String rightKey) {
+ super();
+ _leftKey = leftKey;
+ _rightKey = rightKey;
+ _selector = selector;
+ }
+
+ public String leftKey() {
+ return _leftKey;
+ }
+
+ public String rightKey() {
+ return _rightKey;
+ }
+
+ public NSSelector selector() {
+ return _selector;
+ }
+
+ /**
+ * Evaluates this qualifier for the specified object,
+ * and returns whether the object is qualified.
+ * selector() is invoked on the value for key() on the
+ * specified object, with value() as the parameter.
+ */
+ public boolean evaluateWithObject(Object eo) {
+ try {
+ Object lvalue, rvalue;
+ if ( eo instanceof EOKeyValueCoding) {
+ lvalue = ((EOKeyValueCoding)eo).valueForKey(leftKey());
+ rvalue = ((EOKeyValueCoding)eo).valueForKey(rightKey());
+ } else {
+ lvalue = EOKeyValueCodingSupport.valueForKey(eo, leftKey());
+ rvalue = EOKeyValueCodingSupport.valueForKey(eo, rightKey());
+ }
+ return ((Boolean)_selector.invoke( lvalue, rvalue)).booleanValue();
+ } catch (Exception exc) {
+ throw new WotonomyException( exc );
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyValueArchiving#encodeWithKeyValueArchiver(net.wotonomy.control.EOKeyValueArchiver)
+ */
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EOKeyComparisonQualifier", "class");
+ arch.encodeObject(_leftKey, "key");
+ NSMutableDictionary d = new NSMutableDictionary(2);
+ arch.encodeObject(_rightKey, "value");
+ String selname = null;
+ if (_selector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike))
+ selname = "caseInsensitiveLike:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorContains))
+ selname = "contains:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorEqual))
+ selname = "isEqualTo:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorGreaterThan))
+ selname = "greaterThan:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorGreaterThanOrEqualTo))
+ selname = "greaterThanOrEqualTo:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorLessThan))
+ selname = "lessThan:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorLessThanOrEqualTo))
+ selname = "lessThanOrEqualTo:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorLike))
+ selname = "like:";
+ else if (_selector.equals(EOQualifier.QualifierOperatorNotEqual))
+ selname = "isNotEqualTo:";
+ else
+ selname = _selector.name() + ":";
+ arch.encodeObject(selname, "selectorName");
+ }
+
+ public static Object decodeWithKeyValueArchiver(EOKeyValueUnarchiver arch) {
+ String k = (String)arch.decodeObjectForKey("key");
+ String v = (String)arch.decodeObjectForKey("value");
+ NSSelector sel = null;
+ String sname = (String)arch.decodeObjectForKey("selectorName");
+ if (sname.equals("isEqualTo:"))
+ sel = EOQualifier.QualifierOperatorEqual;
+ else if (sname.equals("isNotEqualTo:"))
+ sel = EOQualifier.QualifierOperatorNotEqual;
+ else if (sname.equals("caseInsensitiveLike:"))
+ sel = EOQualifier.QualifierOperatorCaseInsensitiveLike;
+ else if (sname.equals("contains:"))
+ sel = EOQualifier.QualifierOperatorContains;
+ else if (sname.equals("greaterThan:"))
+ sel = EOQualifier.QualifierOperatorGreaterThan;
+ else if (sname.equals("greaterThanOrEqualTo:"))
+ sel = EOQualifier.QualifierOperatorGreaterThanOrEqualTo;
+ else if (sname.equals("lessThan:"))
+ sel = EOQualifier.QualifierOperatorLessThan;
+ else if (sname.equals("lessThanOrEqualTo:"))
+ sel = EOQualifier.QualifierOperatorLessThanOrEqualTo;
+ else if (sname.equals("like:"))
+ sel = EOQualifier.QualifierOperatorLike;
+ EOKeyComparisonQualifier q = new EOKeyComparisonQualifier(k, sel, v);
+ return q;
+ }
+
+}
+/*
+ * $Log$
+ * 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.1 2003/08/12 01:42:36 chochos
+ * this qualifier was missing
+ *
+ */
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java
new file mode 100644
index 0000000..76c4d05
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java
@@ -0,0 +1,146 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2002 Israfil consulting Services 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
+
+$Id: EOKeyGlobalID.java 894 2006-02-16 16:47:14Z cgruber $
+
+*/
+
+package net.wotonomy.control;
+
+import java.io.ObjectStreamException;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSCoder;
+import net.wotonomy.foundation.NSCoding;
+
+/**
+* The model object which represents the mapping between database
+* fields and object properties, lists available entities, lists
+* connection dictionaries, and has fetch specifications.
+*
+* @author cgruber@israfil.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+
+public abstract class EOKeyGlobalID extends EOGlobalID
+ implements NSCoding {
+
+ protected String _entityName;
+ protected int _hash;
+
+ protected EOKeyGlobalID(String entityName, int hashCode) {
+ super();
+ _entityName = entityName;
+ _hash = hashCode;
+ }
+
+ protected void _prepClone(EOKeyGlobalID gid) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public static EOKeyGlobalID globalIDWithEntityName(String s, Object aobj[]) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public static EOKeyGlobalID _defaultGlobalIDWithEntityName(String s, Object aobj[]) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public String entityName() {
+ return _entityName;
+ }
+
+ public String _literalEntityName() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public boolean _isFinal() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void _setGuessedEntityName(String s) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public String _guessedEntityName() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void _setSubEntityName(String s) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public String _subEntityName() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public int hashCode() {
+ return _hash;
+ }
+
+ public abstract Object[] keyValues();
+
+ public abstract Object[] _keyValuesNoCopy();
+
+ public abstract int keyCount();
+
+ public NSArray keyValuesArray() {
+ return new NSArray(keyValues());
+ }
+
+ public static Object decodeObject(NSCoder nscoder) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public static EOKeyGlobalID _adjustForInheritance(EOKeyGlobalID eokeyglobalid, String s, String s1) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void encodeWithCoder(NSCoder nscoder) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public Class classForCoder() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ protected Object readResolve() throws ObjectStreamException {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+}
+/*
+ * $Log$
+ * 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.2 2003/08/19 01:50:54 chochos
+ * added two concrete implementations of EOKeyGlobalID.
+ *
+ * Revision 1.1 2002/07/14 21:59:06 mpowers
+ * Contributions from cgruber.
+ *
+ * Revision 1.1 2002/06/25 00:11:09 cgruber
+ * Add EOKeyGlobalID, but it won't work without NSCoder.
+ *
+ */
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java
new file mode 100644
index 0000000..3e2f808
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java
@@ -0,0 +1,106 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSSelector;
+
+/**
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOKeyValueArchiver {
+
+ //for the delegate
+ public static final NSSelector sel = new NSSelector("referenceToEncodeForObject",
+ new Class[]{ Object.class });
+ NSMutableDictionary dict = new NSMutableDictionary();
+ private Object _delegate;
+
+ public EOKeyValueArchiver() {
+ super();
+ }
+
+ public void encodeBool(boolean flag, String key) {
+ String v = flag ? "true" : "false";
+ dict.setObjectForKey(v, key);
+ }
+
+ public void encodeInt(int value, String key) {
+ dict.setObjectForKey(new Integer(value), key);
+ }
+
+ public void encodeObject(Object obj, String key) {
+ if (obj == null)
+ return;
+ if (obj instanceof NSArray || obj instanceof NSDictionary) {
+ dict.setObjectForKey(obj, key);
+ return;
+ }
+ if (obj instanceof EOKeyValueArchiving) {
+ EOKeyValueArchiver arch = new EOKeyValueArchiver();
+ ((EOKeyValueArchiving)obj).encodeWithKeyValueArchiver(arch);
+ dict.setObjectForKey(arch.dictionary(), key);
+ return;
+ }
+ if (obj instanceof String)
+ dict.setObjectForKey(obj, key);
+ }
+
+ public void encodeReferenceToObject(Object obj, String key) {
+ if (_delegate != null) {
+ if (sel.implementedByObject(obj)) {
+ try {
+ Object ref = sel.invoke(_delegate, obj);
+ if (ref != null)
+ dict.setObjectForKey(ref, key);
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ public NSDictionary dictionary() {
+ return new NSDictionary(dict);
+ }
+
+ public void setDelegate(Object delegate) {
+ _delegate = delegate;
+ }
+ public Object delegate() {
+ return _delegate;
+ }
+
+}
+/*
+ * $Log$
+ * 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.1 2003/08/09 01:17:53 chochos
+ * Part of the EOKeyValueArchiving protocol (archives objects into NSDictionaries and vice-versa)
+ *
+ */ \ No newline at end of file
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java
new file mode 100644
index 0000000..0d9f78c
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java
@@ -0,0 +1,41 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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;
+
+/**
+ * Remember that this method is also part of the interface:
+ * public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver unarchiver)
+ *
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EOKeyValueArchiving {
+
+ public abstract void encodeWithKeyValueArchiver(EOKeyValueArchiver archiver);
+
+}
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2003/08/09 01:17:53 chochos
+ * Part of the EOKeyValueArchiving protocol (archives objects into NSDictionaries and vice-versa)
+ *
+ */ \ No newline at end of file
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java
new file mode 100644
index 0000000..b3cf926
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java
@@ -0,0 +1,129 @@
+/*
+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 net.wotonomy.foundation.NSKeyValueCoding;
+
+/**
+* EOKeyValueCoding defines an interface for classes that
+* need to have more control over the wotonomy's property
+* introspection facilities. <br><br>
+*
+* On an object that implements this interface, wotonomy
+* will call these methods, and otherwise use the static
+* methods on EOKeyValueCodingSupport. <br><br>
+*
+* EOKeyValueCodingSupport implements the default behaviors
+* for each of these methods, so classes implementing this
+* interface can call those methods to acheive the same
+* behavior. <br><br>
+*
+* valueForKey and takeValueForKey are called in response
+* to user actions, like viewing an object or updating its
+* value in a user interface. These should call the public
+* getter and setter methods on the object itself and the
+* operations should be subject to validation. <br><br>
+*
+* storedValueForKey and takeStoredValueForKey are called
+* in response to wotonomy actions, like snapshotting,
+* faulting, commits, and reverts. These operations should
+* bypass the public methods and directly modify the internal
+* state of the object without validation.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EOKeyValueCoding extends NSKeyValueCoding
+{
+ /**
+ * Returns the value for the specified property.
+ * If the property does not exist, this method should
+ * call handleQueryWithUnboundKey.
+ */
+ Object valueForKey( String aKey );
+
+ /**
+ * Sets the property to the specified value.
+ * If the property does not exist, this method should
+ * call handleTakeValueForUnboundKey.
+ * If the property is of a type that cannot allow
+ * null (e.g. primitive types) and aValue is null,
+ * this method should call unableToSetNullForKey.
+ */
+ void takeValueForKey( Object aValue, String aKey );
+
+ /**
+ * Returns the value for the private field that
+ * corresponds to the specified property.
+ */
+ Object storedValueForKey( String aKey );
+
+ /**
+ * Sets the the private field that corresponds to the
+ * specified property to the specified value.
+ */
+ void takeStoredValueForKey( Object aValue, String aKey );
+
+ /**
+ * Called by valueForKey when the specified key is
+ * not found on this object. Implementing classes
+ * should handle the specified value or otherwise
+ * throw an exception.
+ */
+ Object handleQueryWithUnboundKey( String aKey );
+
+ /**
+ * Called by takeValueForKey when the specified key
+ * is not found on this object. Implementing classes
+ * should handle the specified value or otherwise
+ * throw an exception.
+ */
+ void handleTakeValueForUnboundKey( Object aValue, String aKey );
+
+ /**
+ * Called by takeValueForKey when the type of the
+ * specified key is not allowed to be null, as is
+ * the case with primitive types. Implementing
+ * classes should handle this case appropriately
+ * or otherwise throw an exception.
+ */
+ void unableToSetNullForKey( String aKey );
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.3 2003/01/16 22:47:30 mpowers
+ * Compatibility changes to support compiling woextensions source.
+ * (34 out of 56 classes compile!)
+ *
+ * Revision 1.2 2001/03/28 16:12:30 mpowers
+ * Documented interface.
+ *
+ * Revision 1.1 2001/03/27 23:25:05 mpowers
+ * Contributing interface, no docs yet.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java
new file mode 100644
index 0000000..bc48f58
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java
@@ -0,0 +1,176 @@
+/*
+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.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSKeyValueCodingAdditions;
+
+/**
+* EOKeyValueCodingAdditions defines an interface for classes
+* that need to have more control over the wotonomy's bulk
+* property copying and cloning facilities. <br><br>
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public interface EOKeyValueCodingAdditions extends EOKeyValueCoding, NSKeyValueCodingAdditions
+{
+
+ /**
+ * Static utilities methods that
+ * call the appropriate method if the object implements
+ * NSKeyValueCodingAdditions, otherwise calls the method
+ * on DefaultImplementation.
+ */
+ public class Utility extends NSKeyValueCodingAdditions.Utility
+ {
+ /**
+ * Calls the appropriate method if the object implements
+ * NSKeyValueCodingAdditions, otherwise calls the method
+ * on DefaultImplementation.
+ */
+ public static void takeValuesFromDictionary(
+ Object object, Map dictionary)
+ {
+ if (object instanceof NSKeyValueCodingAdditions) {
+ ((NSKeyValueCodingAdditions)object).takeValuesFromDictionary(dictionary);
+ } else {
+ DefaultImplementation.takeValuesFromDictionary(object, dictionary);
+ }
+ }
+
+ /**
+ * Calls the appropriate method if the object implements
+ * NSKeyValueCodingAdditions, otherwise calls the method
+ * on DefaultImplementation.
+ */
+/*
+ public static void takeValuesFromDictionaryWithMapping(
+ Object object, NSDictionary dictionary, NSDictionary mapping)
+ {
+ if (object instanceof NSKeyValueCodingAdditions) {
+ ((NSKeyValueCodingAdditions)object).takeValuesFromDictionaryWithMapping(dictionary, mapping);
+ } else {
+ DefaultImplementation.takeValuesFromDictionaryWithMapping(object, dictionary, mapping);
+ }
+ }
+*/
+
+ /**
+ * Calls the appropriate method if the object implements
+ * NSKeyValueCodingAdditions, otherwise calls the method
+ * on DefaultImplementation.
+ */
+ public static NSDictionary valuesForKeys(
+ Object object, List keys)
+ {
+ if (object instanceof NSKeyValueCodingAdditions) {
+ return ((NSKeyValueCodingAdditions)object).valuesForKeys(keys);
+ } else {
+ return DefaultImplementation.valuesForKeys(object, keys);
+ }
+ }
+
+ /**
+ * Calls the appropriate method if the object implements
+ * NSKeyValueCodingAdditions, otherwise calls the method
+ * on DefaultImplementation.
+ */
+/*
+ public static NSDictionary valuesForKeysWithMapping(
+ Object object, NSDictionary mapping)
+ {
+ if (object instanceof NSKeyValueCodingAdditions) {
+ return ((NSKeyValueCodingAdditions)object).valuesForKeysWithMapping(mapping);
+ } else {
+ return DefaultImplementation.valuesForKeysWithMapping(object, mapping);
+ }
+ }
+*/
+ }
+
+ /**
+ * Provides a reflection-based implementation for classes that
+ * don't implement NSKeyValueCodingAdditions.
+ */
+ public class DefaultImplementation extends NSKeyValueCodingAdditions.DefaultImplementation
+ {
+ public static void takeValuesFromDictionary(
+ Object object, NSDictionary dictionary)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ public static void takeValuesFromDictionaryWithMapping(
+ Object object, NSDictionary dictionary, NSDictionary mapping)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ public static NSDictionary valuesForKeys(
+ Object object, List keys)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ public static NSDictionary valuesForKeysWithMapping(
+ Object object, Map mapping)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+ }
+}
+
+/*
+ * $Log$
+ * 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.4 2003/01/16 22:47:30 mpowers
+ * Compatibility changes to support compiling woextensions source.
+ * (34 out of 56 classes compile!)
+ *
+ * Revision 1.3 2001/12/10 15:25:11 mpowers
+ * Now properly extending EOKeyValueCoding.
+ *
+ * Revision 1.2 2001/04/28 14:12:23 mpowers
+ * Refactored cloning/copying into KeyValueCodingUtilities.
+ *
+ * Revision 1.1 2001/03/29 03:29:49 mpowers
+ * Now using KeyValueCoding and Support instead of Introspector.
+ *
+ * Revision 1.2 2001/03/28 16:12:30 mpowers
+ * Documented interface.
+ *
+ * Revision 1.1 2001/03/27 23:25:05 mpowers
+ * Contributing interface, no docs yet.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java
new file mode 100644
index 0000000..89e3e91
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java
@@ -0,0 +1,235 @@
+/*
+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 net.wotonomy.foundation.internal.Introspector;
+import net.wotonomy.foundation.internal.IntrospectorException;
+import net.wotonomy.foundation.internal.MissingPropertyException;
+import net.wotonomy.foundation.internal.NullPrimitiveException;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOKeyValueCodingSupport defines default behavior for
+* classes implementing EOKeyValueSupport. <br><br>
+*
+* On an object that does not implement EOKeyValueCoding,
+* wotonomy will call the methods on this class directly.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOKeyValueCodingSupport
+{
+ /**
+ * Returns the value for the specified property key
+ * on the specified object. <br><br>
+ *
+ * If the property does not exist, this method calls
+ * handleQueryWithUnboundKey on the object if it
+ * implements EOKeyValueCoding, otherwise calls
+ * handleQueryWithUnboundKey on this class. <br><br>
+ */
+ static public Object valueForKey(
+ Object anObject, String aKey )
+ {
+ //TODO: may need to handle "." nesting here so
+ // that handleQueryWithUnboundKey gets called for
+ // for the nested object, not the parent object
+
+ //Correction: need to handle key paths in
+ // KeyValueCodingAdditionsSupport.
+
+ try
+ {
+ return Introspector.get( anObject, aKey );
+ }
+ catch ( IntrospectorException exc )
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ return ((EOKeyValueCoding)anObject).handleQueryWithUnboundKey( aKey );
+ }
+ return handleQueryWithUnboundKey( anObject, aKey );
+ }
+ }
+
+ /**
+ * Sets the property to the specified value on
+ * the specified object.
+ *
+ * If the property does not exist, this method calls
+ * handleTakeValueForUnboundKey on the object if it
+ * implements EOKeyValueCoding, otherwise calls
+ * handleTakeValueForUnboundKey on this class.
+ *
+ * If the property is of a type that cannot allow
+ * null (e.g. primitive types) and aValue is null,
+ * this method should call unableToSetNullForKey
+ * on the object if it implements EOKeyValueCoding,
+ * otherwise calls unableToSetNullForKey on this class.
+ */
+ static public void takeValueForKey(
+ Object anObject, Object aValue, String aKey )
+ {
+ //TODO: may need to handle "." nesting here so
+ // that handleTakeValueForUnboundKey gets called for
+ // for the nested object, not the parent object
+
+ try
+ {
+ Introspector.set( anObject, aKey, aValue );
+ }
+ catch ( NullPrimitiveException exc )
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)anObject).unableToSetNullForKey( aKey );
+ }
+ else
+ {
+ unableToSetNullForKey( anObject, aKey );
+ }
+ }
+ catch ( MissingPropertyException exc )
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)anObject).handleTakeValueForUnboundKey(
+ aValue, aKey );
+ }
+ else
+ {
+ handleTakeValueForUnboundKey( anObject, aValue, aKey );
+ }
+ }
+
+ }
+
+ /**
+ * Returns the value for the private field that
+ * corresponds to the specified property on
+ * the specified object.
+ *
+ * This implementation currently calls valueForKey,
+ * because java security currently prevents us from
+ * accessing the fields of another object.
+ */
+ static public Object storedValueForKey(
+ Object anObject, String aKey )
+ {
+ //TODO: this currently just calls valueForKey
+ return valueForKey( anObject, aKey );
+ }
+
+ /**
+ * Sets the the private field that corresponds to the
+ * specified property to the specified value on the
+ * specified object.
+ *
+ * This implementation currently calls takeValueForKey,
+ * because java security currently prevents us from
+ * accessing the fields of another object.
+ */
+ static public void takeStoredValueForKey(
+ Object anObject, Object aValue, String aKey )
+ {
+ //TODO: this currently just calls takeValueForKey
+ takeValueForKey( anObject, aValue, aKey );
+ }
+
+ /**
+ * Called by valueForKey when the specified key is
+ * not found on the specified object, if that object
+ * does not implement EOKeyValueCoding.
+ *
+ * This implementation throws a WotonomyException.
+ */
+ static public Object handleQueryWithUnboundKey(
+ Object anObject, String aKey )
+ {
+ throw new WotonomyException(
+ "Key not found for object: "
+ + aKey + " : " + anObject );
+ }
+
+ /**
+ * Called by takeValueForKey when the specified key
+ * is not found on the specified object, if that object
+ * does not implement EOKeyValueCoding.
+ *
+ * This implementation throws a WotonomyException.
+ */
+ static public void handleTakeValueForUnboundKey(
+ Object anObject, Object aValue, String aKey )
+ {
+ throw new WotonomyException(
+ "Key not found for object while setting value: "
+ + aKey + " : " + anObject + " : " + aValue );
+ }
+
+ /**
+ * Called by takeValueForKey when the type of the
+ * specified key is not allowed to be null, as is
+ * the case with primitive types, if the specified
+ * object does not implement EOKeyValueCoding.
+ *
+ * This implementation throws a WotonomyException.
+ */
+ static public void unableToSetNullForKey(
+ Object anObject, String aKey )
+ {
+ throw new WotonomyException(
+ "Tried to key on object to null: "
+ + aKey + " : " + anObject );
+ }
+
+}
+
+
+/*
+ * $Log$
+ * 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.5 2003/01/16 22:47:30 mpowers
+ * Compatibility changes to support compiling woextensions source.
+ * (34 out of 56 classes compile!)
+ *
+ * Revision 1.4 2001/05/18 21:04:33 mpowers
+ * Reimplemented EditingContext.initializeObject.
+ *
+ * Revision 1.3 2001/04/27 00:28:29 mpowers
+ * Fixed a return value.
+ *
+ * Revision 1.2 2001/04/03 20:36:01 mpowers
+ * Fixed refaulting/reverting/invalidating to be self-consistent.
+ *
+ * Revision 1.1 2001/03/28 17:49:33 mpowers
+ * Implemented EOKeyValueCodingSupport.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java
new file mode 100644
index 0000000..799955e
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java
@@ -0,0 +1,233 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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 net.wotonomy.foundation.NSKeyValueCoding;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOKeyValueQualifier performs a property-based
+* comparison against a specified value. The comparison
+* is specified in the form of a NSSelector. The
+* selector is expected to take two arguments, the
+* property value on an object and the comparison value,
+* and return a Boolean indicating whether the object
+* is qualified. EOQualifier defines selectors that
+* may be used in creating EOKeyValueQualifiers.
+*
+* @author michael@mpowers.net
+* @author yjcheung@intersectsoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOKeyValueQualifier extends EOQualifier
+ implements EOKeyValueArchiving, EOQualifierEvaluation
+{
+ private String key;
+ private NSSelector selector;
+ private Object value;
+
+ /**
+ * Constructor specifying a property key, a selector,
+ * and a value for comparison. The selector may be
+ * one of the constant selectors defined on EOQualifier.
+ */
+ public EOKeyValueQualifier(
+ String aKey,
+ NSSelector aSelector,
+ Object aValue )
+ {
+ key = aKey;
+ selector = aSelector;
+ value = aValue;
+ }
+
+ /**
+ * Returns the key for this qualifier.
+ */
+ public String key()
+ {
+ return key;
+ }
+
+ /**
+ * Returns the selector for this qualifier.
+ */
+ public NSSelector selector()
+ {
+ return selector;
+ }
+
+ /**
+ * Returns the value for this qualifier.
+ */
+ public Object value()
+ {
+ return value;
+ }
+
+ /**
+ * Evaluates this qualifier for the specified object,
+ * and returns whether the object is qualified.
+ * selector() is invoked on the value for key() on the
+ * specified object, with value() as the parameter.
+ */
+ public boolean evaluateWithObject( Object anObject )
+ {
+ try
+ {
+ Object value;
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ value = ((EOKeyValueCoding)anObject).valueForKey( key() );
+ }
+ else
+ {
+ value = EOKeyValueCodingSupport.valueForKey( anObject, key() );
+ }
+ return ((Boolean)selector.invoke( value(), value)).booleanValue();
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+ }
+
+ /**
+ * Returns a string representation of this qualifier.
+ */
+ public String toString()
+ {
+ return "( " + key + " " + selector + " " + value + " )";
+ }
+
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EOKeyValueQualifier", "class");
+ arch.encodeObject(key, "key");
+ NSMutableDictionary d = new NSMutableDictionary(2);
+ if (value instanceof String)
+ d.setObjectForKey("NSString", "class");
+ else if (value instanceof java.math.BigDecimal)
+ d.setObjectForKey("NSDecimalNumber", "class");
+ else if (value instanceof Number)
+ d.setObjectForKey("NSNumber", "class");
+ else if (value instanceof NSKeyValueCoding.Null)
+ d.setObjectForKey("EONull", "class");
+ if (value != null && !(value instanceof NSKeyValueCoding.Null))
+ d.setObjectForKey(value.toString(), "value");
+ arch.encodeObject(d, "value");
+ String selname = null;
+ if (selector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike))
+ selname = "caseInsensitiveLike:";
+ else if (selector.equals(EOQualifier.QualifierOperatorContains))
+ selname = "contains:";
+ else if (selector.equals(EOQualifier.QualifierOperatorEqual))
+ selname = "isEqualTo:";
+ else if (selector.equals(EOQualifier.QualifierOperatorGreaterThan))
+ selname = "greaterThan:";
+ else if (selector.equals(EOQualifier.QualifierOperatorGreaterThanOrEqualTo))
+ selname = "greaterThanOrEqualTo:";
+ else if (selector.equals(EOQualifier.QualifierOperatorLessThan))
+ selname = "lessThan:";
+ else if (selector.equals(EOQualifier.QualifierOperatorLessThanOrEqualTo))
+ selname = "lessThanOrEqualTo:";
+ else if (selector.equals(EOQualifier.QualifierOperatorLike))
+ selname = "like:";
+ else if (selector.equals(EOQualifier.QualifierOperatorNotEqual))
+ selname = "isNotEqualTo:";
+ else
+ selname = selector.name() + ":";
+ arch.encodeObject(selname, "selectorName");
+ }
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) {
+ String k = (String)arch.decodeObjectForKey("key");
+ Object v = arch.decodeObjectForKey("value");
+ NSSelector sel = null;
+ String sname = (String)arch.decodeObjectForKey("selectorName");
+ if (sname.equals("isEqualTo:"))
+ sel = EOQualifier.QualifierOperatorEqual;
+ else if (sname.equals("isNotEqualTo:"))
+ sel = EOQualifier.QualifierOperatorNotEqual;
+ else if (sname.equals("caseInsensitiveLike:"))
+ sel = EOQualifier.QualifierOperatorCaseInsensitiveLike;
+ else if (sname.equals("contains:"))
+ sel = EOQualifier.QualifierOperatorContains;
+ else if (sname.equals("greaterThan:"))
+ sel = EOQualifier.QualifierOperatorGreaterThan;
+ else if (sname.equals("greaterThanOrEqualTo:"))
+ sel = EOQualifier.QualifierOperatorGreaterThanOrEqualTo;
+ else if (sname.equals("lessThan:"))
+ sel = EOQualifier.QualifierOperatorLessThan;
+ else if (sname.equals("lessThanOrEqualTo:"))
+ sel = EOQualifier.QualifierOperatorLessThanOrEqualTo;
+ else if (sname.equals("like:"))
+ sel = EOQualifier.QualifierOperatorLike;
+ EOKeyValueQualifier q = new EOKeyValueQualifier(k, sel, v);
+ return q;
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.10 2003/08/12 01:43:04 chochos
+ * formally implement EOQualifierEvaluation
+ *
+ * Revision 1.9 2003/08/11 19:39:30 chochos
+ * special conditions for NSKeyValueCoding.NullValue -> EONull
+ *
+ * Revision 1.8 2003/08/09 01:22:51 chochos
+ * qualifiers implement EOKeyValueArchiving
+ *
+ * Revision 1.7 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.6 2001/10/31 15:26:06 mpowers
+ * Fixed typo.
+ *
+ * Revision 1.5 2001/10/31 15:25:14 mpowers
+ * Cleanup of qualifiers.
+ *
+ * Revision 1.4 2001/10/30 22:57:28 mpowers
+ * EOQualifier framework is now working.
+ *
+ * Revision 1.3 2001/09/13 15:25:56 mpowers
+ * Started implementation of the EOQualifier framework.
+ *
+ * Revision 1.2 2001/03/29 03:29:49 mpowers
+ * Now using KeyValueCoding and Support instead of Introspector.
+ *
+ * Revision 1.1 2001/02/27 03:33:04 mpowers
+ * Initial draft of the key-value qualifier.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java
new file mode 100644
index 0000000..caed8a4
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java
@@ -0,0 +1,165 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.lang.reflect.Method;
+
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSKeyValueCoding;
+
+/** Creates objects from dictionaries that were created with
+ * EOKeyValueArchiver.
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOKeyValueUnarchiver {
+
+ NSDictionary dict;
+ Object _delegate;
+ protected static final Class[] METHOD_ARGS = new Class[]{ EOKeyValueUnarchiver.class };
+
+ public EOKeyValueUnarchiver(NSDictionary archive) {
+ super();
+ dict = archive;
+ }
+
+ public void awakeObjects() {
+ }
+
+ public boolean decodeBoolForKey(String key) {
+ Object x = dict.objectForKey(key);
+ if (x == null)
+ return false;
+ return (x.equals("true") || x.equals("YES") || x.equals("Y"));
+ }
+
+ public int decodeIntForKey(String key) {
+ Object x = dict.objectForKey(key);
+ if (x == null)
+ return 0;
+ if (x instanceof Number)
+ return ((Number)x).intValue();
+ try {
+ int i = Integer.parseInt(x.toString());
+ return i;
+ } catch (NumberFormatException ex) {
+ return 0;
+ }
+ }
+
+ public Object decodeObjectForKey(String key) {
+ Object x = dict.objectForKey(key);
+ if (x == null)
+ return null;
+ if (x instanceof NSDictionary) {
+ NSDictionary d = (NSDictionary)x;
+ if (d.objectForKey("class") != null) {
+ String cname = d.objectForKey("class").toString();
+ Class _class = null;
+ if (cname.equals("NSNumber")) {
+ if (d.objectForKey("value") != null)
+ return new Long(d.objectForKey("value").toString());
+ } else if (cname.equals("NSDecimalNumber")) {
+ if (d.objectForKey("value") != null)
+ return new java.math.BigDecimal(d.objectForKey("value").toString());
+ } else if (cname.equals("NSString")) {
+ if (d.objectForKey("value") != null)
+ return d.objectForKey("value").toString();
+ } else if (cname.equals("EONull")) {
+ return NSKeyValueCoding.NullValue;
+ } else if (cname.equals("EOFetchSpecification")) {
+ _class = EOFetchSpecification.class;
+ } else if (cname.equals("EOKeyValueQualifier")) {
+ _class = EOKeyValueQualifier.class;
+ } else if (cname.equals("EONotQualifier")) {
+ _class = EONotQualifier.class;
+ } else if (cname.equals("EOAndQualifier")) {
+ _class = EOAndQualifier.class;
+ } else if (cname.equals("EOOrQualifier")) {
+ _class = EOOrQualifier.class;
+ } else if (cname.equals("EOSortOrdering")) {
+ _class = EOSortOrdering.class;
+ } else if (cname.indexOf(".") < 0) { //Load a class without package
+ try {
+ _class = Class.forName("net.wotonomy.control." + cname);
+ } catch (ClassNotFoundException ex) {
+ }
+ //search for the class in access
+ if (_class == null) {
+ try {
+ _class = Class.forName("net.wotonomy.access." + cname);
+ } catch (ClassNotFoundException ex) {
+ }
+ }
+ } else {
+ try {
+ _class = Class.forName(cname);
+ } catch (ClassNotFoundException ex) {
+ }
+ }
+ if (_class == null)
+ return x;
+ try {
+ Method met = _class.getMethod("decodeWithKeyValueUnarchiver", METHOD_ARGS);
+ return met.invoke(null, new Object[]{ new EOKeyValueUnarchiver(d) });
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return x;
+ }
+ }
+ }
+ return x;
+ }
+
+ public Object decodeObjectReferenceForKey(String key) {
+ return null;
+ }
+
+ public void ensureObjectAwake(Object obj) {
+ }
+
+ public void finishInitializationOfObjects() {
+ }
+
+ public Object parent() {
+ return null;
+ }
+
+ public void setDelegate(Object del) {
+ _delegate = del;
+ }
+ public Object delegate() {
+ return _delegate;
+ }
+
+}
+/*
+ * $Log$
+ * 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.2 2003/08/11 18:17:41 chochos
+ * decoding of objects now works properly
+ *
+ */ \ No newline at end of file
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java
new file mode 100644
index 0000000..d086840
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java
@@ -0,0 +1,129 @@
+/*
+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 net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EONotQualifiier negates a specified qualifier,
+* evaluating to the opposite of the specified qualifier.
+*
+* @author michael@mpowers.net
+* @author yjcheung@intersectsoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EONotQualifier extends EOQualifier
+ implements EOKeyValueArchiving, EOQualifierEvaluation
+{
+ private EOQualifier qualifier;
+
+ public EONotQualifier(
+ EOQualifier aQualifier )
+ {
+ qualifier = aQualifier;
+ }
+
+ /**
+ * Returns the qualifier that this qualifier negates.
+ */
+ public EOQualifier qualifier()
+ {
+ return qualifier;
+ }
+
+ /**
+ * Evaluates this qualifier for the specified object,
+ * and returns whether the object is qualified.
+ * evaluateWithObject is invoked on qualifier
+ * and the result is negated and returned.
+ */
+ public boolean evaluateWithObject( Object anObject )
+ {
+ return !(qualifier.evaluateWithObject(anObject));
+ }
+
+ /**
+ * Returns a string representation of this qualifier.
+ */
+ public String toString()
+ {
+ return (new StringBuffer("Not ").append(qualifier.toString()).toString());
+ }
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) {
+ EOQualifier q = (EOQualifier)arch.decodeObjectForKey("qualifier");
+ if (q == null)
+ return null;
+ return new EONotQualifier(q);
+ }
+
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EONotQualifier", "class");
+ if (qualifier instanceof EOKeyValueArchiving) {
+ EOKeyValueArchiver ar2 = new EOKeyValueArchiver();
+ ((EOKeyValueArchiving)qualifier).encodeWithKeyValueArchiver(ar2);
+ arch.encodeObject(ar2.dictionary(), "qualifiers");
+ } else
+ throw new WotonomyException("Cannot archive instance of " + qualifier.getClass().getName());
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.9 2003/08/12 01:43:04 chochos
+ * formally implement EOQualifierEvaluation
+ *
+ * Revision 1.8 2003/08/11 19:39:30 chochos
+ * special conditions for NSKeyValueCoding.NullValue -> EONull
+ *
+ * Revision 1.7 2003/08/09 01:24:19 chochos
+ * implements EOKeyValueArchiving
+ *
+ * Revision 1.6 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.5 2001/10/31 15:25:14 mpowers
+ * Cleanup of qualifiers.
+ *
+ * Revision 1.4 2001/10/30 22:57:28 mpowers
+ * EOQualifier framework is now working.
+ *
+ * Revision 1.3 2001/09/13 15:42:20 mpowers
+ * Fixed another cut/paste typo.
+ *
+ * Revision 1.2 2001/09/13 15:41:34 mpowers
+ * Fixed typo.
+ *
+ * Revision 1.1 2001/09/13 15:38:19 mpowers
+ * Started implementation of the EOQualifier framework.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java
new file mode 100644
index 0000000..3b4544e
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java
@@ -0,0 +1,113 @@
+/*
+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.io.Serializable;
+
+import net.wotonomy.foundation.NSNull;
+
+/**
+* EONullValue is used to represent null in Collections classes
+* because List and Map do not specify whether null values
+* are allowed and because NSArray and NSDictionary explicitly
+* do not allow null values. <br><br>
+*
+* Use of the static singleton method nullValue() is required
+* by this implementation because Java cannot return a singleton
+* instance from a constructor. <br><br>
+*
+* This implementation duplicates NSNull, but the singleton instances
+* are of course different. Be careful. I have no idea why this
+* class was even created, given that NSNull exists.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EONullValue implements Serializable
+{
+ private static final EONullValue instance = new EONullValue();
+
+ /**
+ * Create a new instance of EONullValue.
+ */
+ private EONullValue ()
+ {
+ }
+
+ /**
+ * Constructor specifying name and object.
+ */
+ public static EONullValue nullValue ()
+ {
+ return instance;
+ }
+
+ /**
+ * Returns a human-readable string representation.
+ */
+ public String toString()
+ {
+ return "[null]";
+ }
+
+ /**
+ * Hashcode of all instances is zero.
+ */
+ public int hashCode()
+ {
+ return 0;
+ }
+
+ /**
+ * Implemented to return true for any instance of EONullValue
+ * and for any instance of NSNull.
+ */
+ public boolean equals( Object anObject )
+ {
+ if ( anObject instanceof EONullValue ) return true;
+ if ( anObject instanceof NSNull ) return true;
+ return false;
+ }
+}
+
+/*
+ * $Log$
+ * 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.3 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.2 2001/03/01 20:35:38 mpowers
+ * Implemented equals and hashCode.
+ *
+ * Revision 1.1 2001/02/26 22:41:51 mpowers
+ * Implemented null placeholder classes.
+ * Duplicator now uses NSNull.
+ * No longer catching base exception class.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java
new file mode 100644
index 0000000..cd18e36
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java
@@ -0,0 +1,320 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+
+/**
+* EOObjectStore defines an object repository that tracks
+* object creations, deletions, and updates made by
+* EOEditingContexts. <br><br>
+*
+* A concrete implementation would probably write these
+* changes to some kind of persistent storage, like a
+* database. <br><br>
+*
+* EOEditingContext is itself a subclass of EOObjectStore
+* that requires an EOObjectStore parent for committing
+* its changes. This means that EOEditingContexts can
+* use other EOEditingContexts as their parent, but there
+* still must exist an EOObjectStore as the root of the
+* editing graph.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public abstract class EOObjectStore
+{
+ /**
+ * Key for the user info of ObjectsChangedInStoreNotifications.
+ * The key should retrieve an array of deleted EOGlobalIDs.
+ */
+ public static final String DeletedKey = "deleted";
+
+ /**
+ * Key for the user info of ObjectsChangedInStoreNotifications.
+ * The key should retrieve an array of inserted EOGlobalIDs.
+ */
+ public static final String InsertedKey = "inserted";
+
+ /**
+ * Key for the user info of ObjectsChangedInStoreNotifications.
+ * The key should retrieve an array of updated EOGlobalIDs.
+ * EOEditingContexts should refault their copies of these objects.
+ */
+ public static final String UpdatedKey = "updated";
+
+ /**
+ * Key for the user info of ObjectsChangedInStoreNotification.
+ * The key should retrieve an array of EOGlobalIDs.
+ */
+ public static final String InvalidatedKey = "invalidated";
+
+ /**
+ * Key for the NSNotification posted when this object store
+ * is asked to invalidate all objects. Object of the notification
+ * will be this object store, and user info will contain the
+ * InvalidatedKey.
+ */
+ public static final String
+ InvalidatedAllObjectsInStoreNotification =
+ "EOInvalidatedAllObjectsInStoreNotification";
+
+ /**
+ * Key for the NSNotification posted when this object store
+ * is changed. Object of the notification will be this object
+ * store, and user info will contain InsertedKey, UpdatedKey,
+ * DeletedKey, and InvalidatedKey.
+ */
+ public static final String
+ ObjectsChangedInStoreNotification =
+ "EOObjectsChangedInStoreNotification";
+
+ /**
+ * Default constructor is responsible for initializing
+ * internal state.
+ */
+ public EOObjectStore ()
+ {
+ }
+
+ /**
+ * Called by editing contexts when they no longer
+ * need to track the specified id. You will not need
+ * to call this method, but you use use it for a hint
+ * that the specified global id is not in use by that
+ * child editing context.
+ */
+ public void editingContextDidForgetObjectWithGlobalID (
+ EOEditingContext aContext,
+ EOGlobalID aGlobalID )
+ {
+ }
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship, or may return a placeholder array that
+ * will defer the fetch until accessed (an array fault).
+ * All objects must be registered the specified editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public abstract NSArray arrayFaultWithSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationship,
+ EOEditingContext aContext );
+
+ /**
+ * Returns the object for the specified id.
+ * The returned object may be a fault.
+ * The object will be registered in the
+ * specified editing context.
+ */
+ public abstract /*EOEnterpriseObject*/ Object faultForGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext );
+
+ /**
+ * Returns a fault representing an object of
+ * the specified entity type with values from
+ * the specified dictionary. The fault should
+ * belong to the specified editing context.
+ */
+ public abstract /*EOEnterpriseObject*/ Object faultForRawRow (
+ Map aDictionary,
+ String anEntityName,
+ EOEditingContext aContext );
+
+ /**
+ * Given a newly instantiated object, this method
+ * initializes its properties to values appropriate
+ * for the specified id. The object should already
+ * belong to the specified editing context.
+ * This method is called to populate faults.
+ */
+ public abstract void initializeObject (
+ /*EOEnterpriseObject*/ Object eo,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext );
+
+ /**
+ * Remove all values from all objects in memory,
+ * turning them into faults, and posts an NSNotification
+ * that all objects have been invalidated.
+ * The notification should be named with the string
+ * constant InvalidatedAllObjectsInStoreNotification
+ * with this object store as the object and no user info.
+ */
+ public abstract void invalidateAllObjects ();
+
+ /**
+ * Removes values with the specified ids from memory,
+ * turning them into faults, and posts a notification
+ * that those objects have been invalidated.
+ * The notification should be named with the string
+ * constant ObjectsChangedInStoreNotification
+ * with this object store as the object and user info
+ * containing a key named InvalidateKey that returns
+ * a List of the EOGlobalIDs of the invalidated objects.
+ */
+ public abstract void invalidateObjectsWithGlobalIDs (
+ List aList );
+
+ /**
+ * Returns whether the object corresponding to the
+ * specified id is locked. The concept of object
+ * locking is implementation-specific.
+ */
+ public abstract boolean isObjectLockedWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext );
+
+ /**
+ * Locks the object corresponding to the
+ * specified id is locked. The concept of object
+ * locking is implementation-specific.
+ * The lock may be released when objects are
+ * invalidated or commited, but this behavior
+ * is not required.
+ */
+ public abstract void lockObjectWithGlobalID (
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext );
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship. This method may not return an array fault
+ * because array faults call this method to fetch on demand.
+ * All objects must be registered the specified editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public abstract NSArray objectsForSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationship,
+ EOEditingContext aContext );
+
+ /**
+ * Returns a List of objects the meet the criteria of
+ * the supplied specification. Faults are not allowed in the array.
+ * Each object is registered with the specified editing context.
+ * If any object is already fetched in the specified context,
+ * it is not refetched and that object should be used in the array.
+ */
+ public abstract NSArray objectsWithFetchSpecification (
+ EOFetchSpecification aFetchSpec,
+ EOEditingContext aContext );
+
+ /**
+ * Removes all values from the specified object,
+ * converting it into a fault for the specified id.
+ * New or deleted objects should not be refaulted.
+ */
+ public abstract void refaultObject (
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext );
+
+ /**
+ * Writes all changes in the specified editing context
+ * to the respository. The object store is expected to
+ * post a notification that should be named with the string
+ * constant ObjectsChangedInStoreNotification
+ * with this object store as the object and user info
+ * containing keys named UpdatedKey, InsertedKey, and
+ * DeletedKey that return Lists of the EOGlobalIDs of the
+ * corresponding objects.
+ */
+ public abstract void saveChangesInEditingContext (
+ EOEditingContext aContext );
+}
+
+/*
+ * $Log$
+ * 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.15 2003/12/18 15:37:38 mpowers
+ * Changes to retain ability to work with objects that don't necessarily
+ * implement EOEnterpriseObject. I would still like to preserve this case
+ * for general usage, however the access package is free to assume that
+ * those objects will be EOs and cast appropriately.
+ *
+ * Revision 1.14 2003/08/19 01:53:12 chochos
+ * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
+ *
+ * Revision 1.13 2002/02/13 21:20:15 mpowers
+ * Updated comments.
+ *
+ * Revision 1.12 2001/05/05 23:05:42 mpowers
+ * Implemented Array Faults.
+ *
+ * Revision 1.11 2001/02/21 21:17:32 mpowers
+ * Now retaining a reference to the recent changes observer.
+ * Better documented need to retain reference.
+ * Started implementing notifications.
+ *
+ * Revision 1.10 2001/02/16 22:51:29 mpowers
+ * Now deep-cloning objects passed between editing contexts.
+ *
+ * Revision 1.9 2001/02/16 18:34:19 mpowers
+ * Implementing nested contexts.
+ *
+ * Revision 1.8 2001/02/15 21:13:30 mpowers
+ * First draft implementation is complete. Now on to debugging.
+ *
+ * Revision 1.7 2001/02/14 23:03:02 mpowers
+ * A near-complete first draft of EOEditingContext.
+ *
+ * Revision 1.6 2001/02/13 23:24:29 mpowers
+ * Implementing more of editing context.
+ *
+ * Revision 1.5 2001/02/12 20:36:36 mpowers
+ * Documented methods.
+ *
+ * Revision 1.4 2001/02/09 22:09:34 mpowers
+ * Completed implementation of EOObjectStore.
+ *
+ * Revision 1.3 2001/02/06 15:24:11 mpowers
+ * Widened parameters on abstract method to fix build.
+ *
+ * Revision 1.2 2001/02/06 14:57:42 mpowers
+ * Defined abstract methods.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java
new file mode 100644
index 0000000..e9a3a6a
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java
@@ -0,0 +1,204 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2002 Israfil consulting Services 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
+
+$Id: EOObjectStoreCoordinator.java 894 2006-02-16 16:47:14Z cgruber $
+
+*/
+
+package net.wotonomy.control;
+
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSNotification;
+/**
+* A representation of a channel of communication to the database.
+*
+* @author cgruber@israfil.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+
+public class EOObjectStoreCoordinator extends EOObjectStore {
+
+ public static final String CooperatingObjectStoreWasAddedNotification = "EOCooperatingObjectStoreWasAddedNotification";
+ public static final String CooperatingObjectStoreWasRemovedNotification = "EOCooperatingObjectStoreWasRemovedNotification";
+ public static final String CooperatingObjectStoreNeededNotification = "EOCooperatingObjectStoreNeededNotification";
+ public static final String GlobalIDKey = "globalID";
+ public static final String FetchSpecificationKey = "fetchSpecification";
+ public static final String ObjectKey = "object";
+
+ public static synchronized EOObjectStoreCoordinator defaultCoordinator() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public static synchronized void setDefaultCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public EOObjectStoreCoordinator() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void dispose() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ private NSArray _sources() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public NSArray cooperatingObjectStores() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void addCooperatingObjectStore(EOCooperatingObjectStore eocooperatingobjectstore) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void removeCooperatingObjectStore(EOCooperatingObjectStore eocooperatingobjectstore) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public EOCooperatingObjectStore objectStoreForGlobalID(EOGlobalID eoglobalid) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public EOCooperatingObjectStore objectStoreForObject(EOEnterpriseObject eoenterpriseobject) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public EOCooperatingObjectStore objectStoreForFetchSpecification(EOFetchSpecification eofetchspecification) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ EOCooperatingObjectStore objectStoreForEntityNamed(String s) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void forwardUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void saveChangesInEditingContext(EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public NSArray objectsWithFetchSpecification(EOFetchSpecification eofetchspecification, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public boolean isObjectLockedWithGlobalID(EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void lockObjectWithGlobalID(EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public /*EOEnterpriseObject*/ Object faultForGlobalID(EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public /*EOEnterpriseObject*/ Object faultForRawRow(NSDictionary nsdictionary, String s, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public NSArray arrayFaultWithSourceGlobalID(EOGlobalID eoglobalid, String s, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void editingContextDidForgetObjectWithGlobalID(EOEditingContext eoeditingcontext, EOGlobalID eoglobalid) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public NSArray objectsForSourceGlobalID(EOGlobalID eoglobalid, String s, EOEditingContext eoeditingcontext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void invalidateObjectsWithGlobalIDs(NSArray nsarray) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void invalidateAllObjects() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void _objectsChangedInSubStore(NSNotification nsnotification) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void _invalidatedAllObjectsInSubStore(NSNotification nsnotification) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public void setUserInfo(NSDictionary nsdictionary) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ public NSDictionary userInfo() {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+ /**
+ * @see net.wotonomy.control.EOObjectStore#faultForRawRow(Map, String, EOEditingContext)
+ */
+ public /*EOEnterpriseObject*/ Object faultForRawRow(
+ Map aDictionary,
+ String anEntityName,
+ EOEditingContext aContext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+
+ /**
+ * @see net.wotonomy.control.EOObjectStore#initializeObject(Object, EOGlobalID, EOEditingContext)
+ */
+ public void initializeObject(
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+
+ /**
+ * @see net.wotonomy.control.EOObjectStore#invalidateObjectsWithGlobalIDs(List)
+ */
+ public void invalidateObjectsWithGlobalIDs(List aList) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+
+ /**
+ * @see net.wotonomy.control.EOObjectStore#refaultObject(Object, EOGlobalID, EOEditingContext)
+ */
+ public void refaultObject(
+ Object anObject,
+ EOGlobalID aGlobalID,
+ EOEditingContext aContext) {
+ throw new UnsupportedOperationException("Not Yet Implemented");
+ }
+
+
+}
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java
new file mode 100644
index 0000000..d42130c
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java
@@ -0,0 +1,547 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 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.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+
+/**
+* EOObserverCenter is a static singleton-like class
+* that manages EOObservings that want to be notified
+* of changes to those objects that call
+* notifyObserversObjectWillChange() before their
+* properties change. <br><br>
+*
+* Implementation note: Because Java implements the
+* Observer pattern in java.util.Observable, this
+* class knows how to register itself as an Observer
+* with Observables as well. However, users should
+* note that Observables only notify their Observers
+* of changes after their property has changed.
+* EODelayedObservers would see no difference because
+* they always receive their notification after all
+* changes have taken place. <br><br>
+*
+* This implementation uses weak references for observers
+* and observables. The advantage to this approach is
+* that you do not need to explicitly unregister observers
+* or observables; they will be unregistered when they are
+* garbage-collected. Note that you will need to retain
+* a reference to any objects you register or they may
+* become unregistered if no other object references them.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOObserverCenter implements Observer
+{
+ /**
+ * Would much rather use a WeakHashMap, but that class
+ * compares by value, and we need to compare by reference.
+ * This means we need to recreate a weak hashmap with
+ * the ReferenceKey class below. Using hashtable for
+ * thread safety.
+ */
+ private static Map observableToObservers = new Hashtable();
+
+ private static List omniscients = new LinkedList();
+
+ // suppression count
+ private static int suppressions = 0;
+
+ // singleton instance - needed for Observer
+ private static EOObserverCenter instance = null;
+
+ // optimization: remember last request and result
+ private static Object lastRequest;
+ private static NSArray lastResult;
+
+ /**
+ * Registers the specified EOObserving for
+ * notifications from the specified object.
+ * An EOObserving can only be registered
+ * once for a given object.
+ */
+ public static void addObserver(
+ EOObserving anObserver, Object anObject )
+ {
+ // atomic operation
+ synchronized ( instance() )
+ {
+ // find observer list
+ List observers = (List)
+ observableToObservers.get( new ReferenceKey( anObject ) );
+
+ // if observer list not found, create and add item
+ if ( observers == null )
+ {
+ observers = new ArrayList();
+ observers.add( new WeakReference( anObserver ) );
+ processQueue();
+ observableToObservers.put( new ReferenceKey( anObject, queue ), observers );
+
+ // support for java.util.Observable
+ if ( anObject instanceof Observable )
+ {
+ ((Observable)anObject).addObserver( instance() );
+ }
+ }
+ else // observer list found - scan for observer
+ if ( indexOf( observers, anObserver ) < 0 )
+ {
+ // observer not found, register it
+ observers.add( new WeakReference( anObserver ) );
+ }
+
+ lastRequest = null;
+ lastResult = null;
+ }
+ }
+
+ /**
+ * Registers the specified EOObserving for notifications
+ * on all object changes. An EOObserving can be
+ * registered as an omniscient observer at most once.
+ * Use omniscient observers with caution.
+ */
+ public static void addOmniscientObserver(
+ EOObserving anObserver )
+ {
+ if ( indexOf( omniscients, anObserver ) < 0 )
+ {
+ omniscients.add( anObserver );
+ }
+ }
+
+ /**
+ * Notifies all EOObservings registered for
+ * notifications for the specified object.
+ * This method is typically called by objects
+ * that wish to broadcast a notification before
+ * a property change takes place, passing itself
+ * as the argument.
+ */
+ public static void notifyObserversObjectWillChange(
+ Object anObject )
+ {
+ if ( observerNotificationSuppressCount() == 0 )
+ {
+ List observers = observersForObject( anObject );
+ EOObserving o;
+ Iterator it = observers.iterator();
+ while ( it.hasNext() )
+ {
+ o = (EOObserving) it.next();
+ o.objectWillChange( anObject );
+ }
+ }
+ }
+
+ /**
+ * Returns an observer that is an instance of
+ * the specified class and
+ * that is registered for notifications about
+ * the specified object. If more than one
+ * such observer exists, which observer is
+ * returned is undetermined - use
+ * observersForObject instead. If no observer
+ * exists, returns null.
+ */
+ public static EOObserving observerForObject(
+ Object anObject, Class aClass )
+ {
+ List result = observersForObject( anObject );
+ if ( result.size() == 0 ) return null;
+
+ Object o;
+ Iterator it = result.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ if ( aClass.isAssignableFrom( o.getClass() ) )
+ {
+ return (EOObserving) o;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the number of times that notifications
+ * have been suppressed. This is also the number
+ * of times that enableObserverNotification must
+ * be called to allow notifications to take place.
+ */
+ public static int observerNotificationSuppressCount()
+ {
+ return suppressions;
+ }
+
+ /**
+ * Returns a List of observers for the
+ * specified object. Returns an empty List
+ * if no observer has registered for that
+ * object.
+ */
+ public static NSArray observersForObject(
+ Object anObject )
+ {
+ synchronized ( instance() )
+ {
+ // optimization: this is called very frequently
+ // from the same object calling willChange() a
+ // number of times in a row as it is updating.
+ if ( lastRequest == anObject )
+ {
+ return lastResult;
+ }
+
+ NSArray result;
+
+ List references = observerListForObject( anObject );
+ if ( references == null )
+ {
+ result = NSArray.EmptyArray;
+ }
+ else
+ {
+ result = new NSMutableArray();
+ Object observer;
+ Iterator it = references.iterator();
+ while ( it.hasNext() )
+ {
+ observer = ((Reference)it.next()).get();
+ if ( observer != null )
+ {
+ result.add( observer );
+ }
+ else // reference has expired
+ {
+ processQueue();
+ it.remove(); // remove from list
+ // if last observer, unregister observable
+ if ( references.size() == 0 )
+ {
+ observableToObservers.remove( new ReferenceKey( anObject ) );
+ }
+ }
+ }
+ }
+
+ lastRequest = anObject;
+ lastResult = result;
+
+ return result;
+ }
+
+ }
+
+ /**
+ * Returns a reference to the actual list of
+ * observers for the given object, or null if it
+ * doesn't exist.
+ */
+ private static List observerListForObject( Object anObject )
+ {
+ return (List) observableToObservers.get( new ReferenceKey( anObject ) );
+ }
+
+ /**
+ * Unregisters the specified observer for
+ * notifications from the specified object.
+ */
+ public static void removeObserver(
+ EOObserving anObserver, Object anObject )
+ {
+ // atomic operation
+ synchronized ( instance() )
+ {
+ lastRequest = null;
+ lastResult = null;
+
+ List result = observerListForObject( anObject );
+ if ( result == null ) return;
+ int index = indexOf( result, anObserver );
+ if ( index == -1 ) return;
+
+ // remove observer from list
+ result.remove( index );
+
+ // if last observer, unregister observable
+ if ( result.size() == 0 )
+ {
+ processQueue();
+ observableToObservers.remove( new ReferenceKey( anObject ) );
+ }
+ }
+ }
+
+ /**
+ * Unregisters the specified omniscient observer.
+ */
+ public static void removeOmniscientObserver(
+ EOObserving anObserver )
+ {
+ int index = indexOf( omniscients, anObserver );
+ if ( index != -1 )
+ {
+ omniscients.remove( index );
+ }
+ }
+
+ /**
+ * Enables notifications after they have been
+ * suppressed by suppressObserverNotification.
+ * If notifications have been suppressed
+ * multiple times, this method must be called
+ * an equal number of times to resume notifications.
+ * If notifications are not currently suppressed,
+ * this method does nothing.
+ */
+ public static void enableObserverNotification()
+ {
+ if ( suppressions > 0 ) suppressions--;
+ }
+
+ /**
+ * Causes notifications to be suppressed until
+ * the next matching call to enableObserverNotification.
+ * If this method is called more than once,
+ * enableObserverNotification must be called an
+ * equal number of times for notifications to resume.
+ * This method always causes notifications to cease
+ * immediately.
+ */
+ public static void suppressObserverNotification()
+ {
+ suppressions++;
+ }
+
+ /**
+ * Because we're comparing by reference, we need to
+ * test for the existence of the object directly.
+ * @return the index or -1 if not found.
+ */
+ private static int indexOf( List aList, Object anObject )
+ {
+ if ( anObject == null ) return -1;
+
+ synchronized ( aList )
+ {
+ int len = aList.size();
+ for ( int i = 0; i < len; i++ )
+ {
+ // compare by reference
+ if ( anObject == ((Reference)aList.get(i)).get() )
+ {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Private singleton instance, so we can be an observer.
+ */
+ private static EOObserverCenter instance()
+ {
+ if ( instance == null )
+ {
+ instance = new EOObserverCenter();
+ }
+ return instance;
+ }
+
+ /**
+ * Interface Observer
+ */
+ public void update( Observable o, Object arg )
+ {
+ notifyObserversObjectWillChange( o );
+ }
+
+ /* Reference queue for cleared WeakKeys */
+ private static ReferenceQueue queue = new ReferenceQueue();
+
+ /* Remove all invalidated entries from the map, that is, remove all entries
+ whose keys have been discarded. This method should be invoked once by
+ each public mutator in this class. We don't invoke this method in
+ public accessors because that can lead to surprising
+ ConcurrentModificationExceptions. */
+ private static void processQueue()
+ {
+ synchronized ( instance() )
+ {
+ ReferenceKey rk;
+ while ((rk = (ReferenceKey)queue.poll()) != null)
+ {
+ //System.out.println( "EOObserverCenter.processQueue: removing object" );
+ observableToObservers.remove(rk);
+ }
+ }
+ }
+
+ /**
+ * Private class used to force a hashmap to
+ * perform key comparisons by reference.
+ * Retains a weak reference just like WeakHashMap.
+ */
+ static private class ReferenceKey extends WeakReference
+ {
+ private int hashCode;
+
+ /**
+ * Called to create a disposable reference key,
+ * used for retrieving values from the hashtable.
+ */
+ public ReferenceKey( Object anObject )
+ {
+ super( anObject );
+ hashCode = anObject.hashCode();
+ }
+
+ /**
+ * Called to create a reference key that will be
+ * used as a key in the hashtable, so we need the
+ * reference queue to later remove the key from the
+ * table when referred object is no longer in use.
+ */
+ public ReferenceKey( Object anObject, ReferenceQueue aQueue )
+ {
+ super( anObject, aQueue );
+ hashCode = anObject.hashCode();
+ }
+
+ /**
+ * Passes through to actual key's hash code.
+ */
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ /**
+ * Compares by reference.
+ */
+ public boolean equals( Object anObject )
+ {
+ if ( ! ( anObject instanceof ReferenceKey ) ) return false;
+ Object key = get();
+ if ( key == null ) return false;
+ return ( key == ((ReferenceKey)anObject).get() );
+ }
+ }
+
+ private static String debugString()
+ {
+ String result = "";
+ int count = 0;
+ synchronized ( instance() )
+ {
+ Object anObject;
+ Iterator it = observableToObservers.keySet().iterator();
+ while ( it.hasNext() )
+ {
+ result += ((Reference)it.next()).get() + " : ";
+ count++;
+ /*
+ Iterator values = ((List)it.next()).iterator();
+ while ( values.hasNext() )
+ {
+ anObject = ((Reference)values.next()).get();
+ if ( anObject != null )
+ {
+// if ( anObject instanceof net.wotonomy.ui.MasterDetailAssociation )
+ result += anObject.getClass().toString() + " : ";
+ count++;
+ }
+ }
+ */
+ }
+ result += "["+count+"]";
+ }
+ return result;
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.9 2002/10/24 18:18:12 mpowers
+ * NSArray's are now considered read-only, so we can return our internal
+ * representation to reduce unnecessary object allocation.
+ *
+ * Revision 1.8 2001/02/21 21:17:32 mpowers
+ * Now retaining a reference to the recent changes observer.
+ * Better documented need to retain reference.
+ * Started implementing notifications.
+ *
+ * Revision 1.7 2001/02/17 16:52:05 mpowers
+ * Changes in imports to support building with jdk1.1 collections.
+ *
+ * Revision 1.6 2001/02/05 18:45:45 mpowers
+ * Reduced access back to private for utility methods.
+ *
+ * Revision 1.5 2001/02/05 18:42:32 mpowers
+ * Updated documentation throughout project.
+ *
+ * Revision 1.4 2001/01/18 16:57:47 mpowers
+ * Added debug facility.
+ *
+ * Revision 1.3 2001/01/10 16:28:53 mpowers
+ * Implemented a compare-by-reference weak hashtable
+ * because WeakHashMap compared by value and we can't
+ * assume that display groups won't contain objects
+ * whose values are equivalent.
+ *
+ * Revision 1.2 2001/01/09 20:10:19 mpowers
+ * Now using weak references to track observables and their observers.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:44 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.8 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java
new file mode 100644
index 0000000..e616a33
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java
@@ -0,0 +1,104 @@
+/*
+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 net.wotonomy.foundation.NSSelector;
+
+/**
+* A convenience observer for objects that do not or cannot
+* subclass EODelayedObserver. EOObserverProxy will invoke
+* an NSSelector on an object when it receives a subjectChanged
+* message.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public class EOObserverProxy
+ extends EODelayedObserver
+{
+ protected Object target;
+ protected NSSelector selector;
+ protected int priority;
+
+ /**
+ * Constructs an EODelayedObserver that will invoke the specified selector
+ * on the specified object, and will run at the specified priority.
+ */
+ public EOObserverProxy (
+ Object anObject, NSSelector aSelector, int aPriority )
+ {
+ target = anObject;
+ selector = aSelector;
+ priority = aPriority;
+ }
+
+ /**
+ * Constructs an EODelayedObserver that will invoke the specified selector
+ * on the specified object, and will run at ObserverPriorityThird priority,
+ * which is the default.
+ */
+ public EOObserverProxy (
+ Object anObject, NSSelector aSelector )
+ {
+ this( anObject, aSelector, ObserverPriorityThird );
+ }
+
+ /**
+ * Returns the priority of this observer in the queue.
+ * This implementation returns the priority specified
+ * in the constructor.
+ */
+ public int priority ()
+ {
+ return priority;
+ }
+
+ /**
+ * Notifies observer that one or more objects that
+ * it is observing have changed. The observer should
+ * check all objects it is observing for changes.
+ */
+ public void subjectChanged ()
+ {
+ try
+ {
+ selector.invoke( target );
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "Error notifying observer: " );
+ exc.printStackTrace();
+ }
+ }
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2001/10/22 21:55:32 mpowers
+ * This turns out to be a really useful class.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java
new file mode 100644
index 0000000..8ec6e5c
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java
@@ -0,0 +1,51 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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;
+
+/**
+* A pure java implementation of EOObserving.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EOObserving
+{
+ /**
+ * Called when the specified object is about to change.
+ */
+ void objectWillChange ( Object anObject );
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:44 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java
new file mode 100644
index 0000000..7f4b1c6
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java
@@ -0,0 +1,169 @@
+/*
+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.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOOrQualifier contains other EOQualifiers,
+* evaluating as true if any of the contained
+* qualifiers evaluate as true.
+*
+* @author michael@mpowers.net
+* @author yjcheung@intersectsoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOOrQualifier extends EOQualifier
+ implements EOKeyValueArchiving, EOQualifierEvaluation
+{
+ private List qualifiers;
+
+ public EOOrQualifier(
+ List aQualifierList )
+ {
+ qualifiers = new LinkedList( aQualifierList );
+ }
+
+ /**
+ * Returns a List of qualifiers contained by this qualifier.
+ */
+ public NSArray qualifiers()
+ {
+ return new NSArray( qualifiers );
+ }
+
+ /**
+ * Add a new qualifier to the list.
+ */
+ public void addQualifier(EOQualifier qualifier)
+ {
+ qualifiers.add(qualifier);
+ }
+
+ /**
+ * Evaluates this qualifier for the specified object,
+ * and returns whether the object is qualified.
+ * selector() is invoked on the value for key() on the
+ * specified object, with value() as the parameter.
+ *
+ * Note: this has a lazy "or" implementation. Ex. Qal1 or Qal2.
+ * If Qal1 is evaluated to be true, then it returns true without
+ * further evaluate Qal2.
+ */
+ public boolean evaluateWithObject( Object anObject )
+ {
+ Iterator it = qualifiers.iterator();
+ while( it.hasNext() )
+ {
+ if ( ((EOQualifier) it.next()).evaluateWithObject(anObject) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a string representation of this qualifier.
+ */
+ public String toString()
+ {
+ StringBuffer myBuf = new StringBuffer("(");
+ Iterator it = qualifiers.iterator();
+ while (it.hasNext())
+ {
+ myBuf = myBuf.append(((EOQualifier) it.next()).toString()).append(" or ");
+ }
+ String myStr = myBuf.toString();
+ myStr = myStr.substring(0, myStr.lastIndexOf(" or ")).concat(")");
+ return myStr;
+ }
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) {
+ NSArray a = (NSArray)arch.decodeObjectForKey("qualifiers");
+ if (a == null)
+ return null;
+ NSMutableArray l = new NSMutableArray();
+ for (int i = 0; i < a.count(); i++) {
+ NSDictionary d = (NSDictionary)a.objectAtIndex(i);
+ EOKeyValueUnarchiver ua = new EOKeyValueUnarchiver(d);
+ EOQualifier q = (EOQualifier)EOQualifier.decodeWithKeyValueUnarchiver(ua);
+ if (q != null)
+ l.addObject(q);
+ }
+ return new EOAndQualifier(l);
+ }
+
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EOOrQualifier", "class");
+ NSMutableArray arr = new NSMutableArray(qualifiers.size());
+ for (int i = 0; i < qualifiers.size(); i++) {
+ EOQualifier q = (EOQualifier)qualifiers.get(i);
+ if (q instanceof EOKeyValueArchiving) {
+ EOKeyValueArchiver ar2 = new EOKeyValueArchiver();
+ ((EOKeyValueArchiving)q).encodeWithKeyValueArchiver(ar2);
+ arr.addObject(ar2.dictionary());
+ } else
+ throw new WotonomyException("Cannot archive instance of " + q.getClass().getName());
+ }
+ arch.encodeObject(arr, "qualifiers");
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.6 2003/08/12 01:43:04 chochos
+ * formally implement EOQualifierEvaluation
+ *
+ * Revision 1.5 2003/08/09 01:22:51 chochos
+ * qualifiers implement EOKeyValueArchiving
+ *
+ * Revision 1.4 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.3 2001/10/31 15:25:14 mpowers
+ * Cleanup of qualifiers.
+ *
+ * Revision 1.2 2001/10/30 22:57:28 mpowers
+ * EOQualifier framework is now working.
+ *
+ * Revision 1.1 2001/09/13 15:25:56 mpowers
+ * Started implementation of the EOQualifier framework.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java
new file mode 100644
index 0000000..6fffea4
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java
@@ -0,0 +1,680 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.NSSet;
+import net.wotonomy.foundation.internal.ValueConverter;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* EOQualifiers are used to perform property-based
+* qualifications on objects: for a set of criteria,
+* a qualifier either qualifies or disqualifies an
+* given object. EOKeyValueQualifiers can be joined
+* by EOAndQualifier and EOOrQualifier, and so can
+* form a tree of qualifications. <br><br>
+*
+* Certain qualifiers
+* can accept a variable in place of a value; variable
+* names are marked by a "$", as in "$name". Variables
+* are resolved with the qualifierWithBindings() method.
+*
+* @author michael@mpowers.net
+* @author yjcheung@intersectsoft.com
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public abstract class EOQualifier
+{
+ public static final NSSelector
+ QualifierOperatorCaseInsensitiveLike = new OperatorCaseInsensitiveLike();
+ public static final NSSelector
+ QualifierOperatorContains = new OperatorContains();
+ public static final NSSelector
+ QualifierOperatorEqual = new OperatorEqual();
+ public static final NSSelector
+ QualifierOperatorGreaterThan = new OperatorGreaterThan();
+ public static final NSSelector
+ QualifierOperatorGreaterThanOrEqualTo = new OperatorGreaterThanOrEqualTo();
+ public static final NSSelector
+ QualifierOperatorLessThan = new OperatorLessThan();
+ public static final NSSelector
+ QualifierOperatorLessThanOrEqualTo = new OperatorLessThanOrEqualTo();
+ public static final NSSelector
+ QualifierOperatorLike = new OperatorLike();
+ public static final NSSelector
+ QualifierOperatorNotEqual = new OperatorNotEqual();
+
+ /**
+ * Default constructor.
+ */
+ public EOQualifier ()
+ {
+ }
+
+ /**
+ * Adds all qualifier keys in this qualifier to
+ * the specified Set, which is expected to be
+ * mutable. The tree of qualifiers is traversed
+ * and the left-hand-side of each expression is
+ * added to the set.
+ */
+ public void addQualifierKeysToSet ( Set aSet )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Set of all property names used for
+ * comparisons by this qualifier. The tree of
+ * qualifiers is traversed and the left-hand-side
+ * of each expression is added to the set.
+ */
+ public NSSet allQualifierKeys ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a List containing the variables used
+ * at compare-time by this qualifier. Each variable
+ * will appear only once in the list.
+ */
+ public NSArray bindingKeys ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether the specified object meets the
+ * criteria defined by this qualifier.
+ */
+ public boolean evaluateWithObject ( Object anObject )
+ {
+ return true;
+ }
+
+ /**
+ * Returns the key (which can be a key path) that
+ * is tested against the specified binding variable.
+ * The tree is traversed looking for the first instance
+ * of the specified variable, and the corresponding
+ * left-hand-side of the expression is returned.
+ */
+ public String keyPathForBindingKey ( String aVariable )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a qualifier that is like this qualifier,
+ * except all variables will be replaced with values
+ * from the specified Map whose keys match the variable
+ * names. If requireAll is true, an exception will be
+ * thrown if there is no key that matches on of the
+ * variables in the tree; otherwise, the qualifier
+ * containing the unmatched variable is removed.
+ */
+ public EOQualifier qualifierWithBindings (
+ Map aMap,
+ boolean requireAll )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+
+ /**
+ * Tests whether all the keys in this qualifier can
+ * be applied to an object of the specified class.
+ * @return A Throwable if the validation fails,
+ * otherwise returns null if the class can be used
+ * with this qualifier.
+ */
+ public Throwable validateKeysWithRootClassDescription (
+ Class aClass )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+
+ // statics
+
+ /**
+ * Convenience to retain only those objects from the specified
+ * List that meet the specified qualifier's requirements.
+ */
+ public static void filterArrayWithQualifier (
+ List anObjectList, EOQualifier aQualifier )
+ {
+ ListIterator iterator = anObjectList.listIterator();
+ while ( iterator.hasNext() )
+ {
+ if ( ! aQualifier.evaluateWithObject( iterator.next() ) )
+ {
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Convenience to return a List consisting only
+ * of those objects in the specified List that meet
+ * the specified qualifier's requirements.
+ */
+ public static NSArray filteredArrayWithQualifier (
+ List anObjectList, EOQualifier aQualifier )
+ {
+ Object o;
+ List result = new LinkedList();
+ Iterator iterator = anObjectList.iterator();
+ while ( iterator.hasNext() )
+ {
+ o = iterator.next();
+ if ( aQualifier.evaluateWithObject( o ) )
+ {
+ result.add( o );
+ }
+ }
+ return new NSArray( (Collection) result );
+ }
+
+ /**
+ * Convenience to create a set of EOKeyValueQualifiers
+ * joined by an EOAndQualifier. Each pair of keys and
+ * values are used to create EOKeyValueQualifiers.
+ */
+ public static EOQualifier
+ qualifierToMatchAllValues ( Map aMap )
+ {
+ Object key, value;
+ List qualifierList = new LinkedList();
+ Iterator iterator = aMap.keySet().iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next();
+ value = aMap.get( key );
+ qualifierList.add( new EOKeyValueQualifier(
+ key.toString(), QualifierOperatorEqual, value ) );
+ }
+ return new EOAndQualifier( qualifierList );
+ }
+
+ /**
+ * Convenience to create a set of EOKeyValueQualifiers
+ * joined by an EOOrQualifier. Each pair of keys and
+ * values are used to create EOKeyValueQualifiers.
+ */
+ public static EOQualifier
+ qualifierToMatchAnyValue ( Map aMap )
+ {
+ Object key, value;
+ List qualifierList = new LinkedList();
+ Iterator iterator = aMap.keySet().iterator();
+ while ( iterator.hasNext() )
+ {
+ key = iterator.next();
+ value = aMap.get( key );
+ qualifierList.add( new EOKeyValueQualifier(
+ key.toString(), QualifierOperatorEqual, value ) );
+ }
+ return new EOOrQualifier( qualifierList );
+ }
+
+ /**
+ * Returns an EOQualifier that meets the criteria
+ * represented by the specified string and variable
+ * length argument list. This method parses the string
+ * and returns a tree of qualifiers. Each token beginning
+ * with "%" is replaced with the corresponding object
+ * from the argument list. The parser recognizes the
+ * operation tokens returned from the allQualifierOperators()
+ * method.
+ */
+ public static EOQualifier qualifierWithQualifierFormat (
+ String aString, List anArgumentList )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a List of operators that are supported for
+ * relational operations. This excludes string comparison
+ * operators.
+ */
+ public static NSArray relationalQualifierOperators ()
+ {
+ NSMutableArray result = new NSMutableArray();
+ BaseSelector selector;
+ Iterator iterator = allQualifierOperators().iterator();
+ while ( iterator.hasNext() )
+ {
+ selector = (BaseSelector) iterator.next();
+ if ( selector.isRelationalOperator() )
+ {
+ result.addObject( selector );
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a List of valid operators.
+ */
+ public static NSArray allQualifierOperators ()
+ {
+ return operators.allKeys();
+ }
+
+ /**
+ * Returns a selector the corresponds to the operation
+ * represented by the specified string. For example,
+ * ">" represents QualifierOperatorGreaterThan.
+ */
+ public static NSSelector operatorSelectorForString (
+ String anOperatorString )
+ {
+ return (NSSelector)
+ operators.objectForKey( anOperatorString );
+ }
+
+ /**
+ * Returns a string the corresponds to the operation
+ * represented by the specified selector. For example,
+ * QualifierOperatorGreaterThan is represented with ">".
+ */
+ public static String stringForOperatorSelector (
+ NSSelector aSelector )
+ {
+ return (String)
+ operators.allKeysForObject( aSelector ).lastObject();
+ }
+
+ /**
+ * Returns a string representation of this qualifier.
+ */
+ public String toString()
+ {
+ //TODO: implement this
+ return super.toString();
+ }
+
+ // built-in qualifiers
+
+ private static NSMutableDictionary operators;
+ static
+ {
+ operators = new NSMutableDictionary();
+ operators.setObjectForKey(
+ QualifierOperatorCaseInsensitiveLike,
+ QualifierOperatorCaseInsensitiveLike.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorContains,
+ QualifierOperatorContains.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorEqual,
+ QualifierOperatorEqual.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorGreaterThan,
+ QualifierOperatorGreaterThan.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorGreaterThanOrEqualTo,
+ QualifierOperatorGreaterThanOrEqualTo.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorLessThan,
+ QualifierOperatorLessThan.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorLessThanOrEqualTo,
+ QualifierOperatorLessThanOrEqualTo.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorLike,
+ QualifierOperatorLike.toString() );
+ operators.setObjectForKey(
+ QualifierOperatorNotEqual,
+ QualifierOperatorNotEqual.toString() );
+ }
+
+ static private abstract class BaseSelector extends NSSelector
+ {
+ public String name ()
+ {
+ return "BaseSelector";
+ }
+
+ public Class[] parameterTypes ()
+ {
+ return new Class[] { Object.class, Object.class };
+ }
+
+ public Method methodOnClass (Class aClass)
+ throws NoSuchMethodException
+ {
+ throw new NoSuchMethodException();
+ }
+
+ public Method methodOnObject (Object anObject)
+ throws NoSuchMethodException
+ {
+ throw new NoSuchMethodException();
+ }
+
+ public boolean implementedByClass (Class aClass)
+ {
+ return true;
+ }
+
+ public boolean implementedByObject (Object anObject)
+ {
+ return true;
+ }
+
+ public boolean isRelationalOperator()
+ {
+ return true;
+ }
+
+ public Object invoke (Object anObject, Object[] parameters)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException
+ {
+ return qualify( anObject, parameters[0] );
+ }
+
+ abstract protected Boolean qualify( Object target, Object parameter );
+
+ protected int doCompare(Object o1, Object o2)
+ {
+ Class firstClass = o1.getClass();
+ Class secondClass = o2.getClass();
+
+ if ( ! ( secondClass.equals( firstClass ) ) )
+ {
+ Object converted = null;
+ if ( o2 instanceof Comparable )
+ {
+ converted = ValueConverter.convertObjectToClass( o1, secondClass );
+ if (converted != null)
+ {
+ o1 = converted;
+ }
+ }
+
+ if (converted == null && (o1 instanceof Comparable))
+ {
+ converted = ValueConverter.convertObjectToClass( o2, firstClass );
+ if ( converted != null )
+ {
+ o2 = converted;
+ }
+ }
+
+ if (converted == null)
+ {
+ throw new WotonomyException("Qualifier: Not Comparable Objects");
+ // no way to compare
+ }
+ }
+
+ return ((Comparable)o2).compareTo( o1 );
+ }
+
+ }
+
+
+ static class OperatorCaseInsensitiveLike extends BaseSelector {
+
+ public boolean isRelationalOperator()
+ {
+ return false;
+ }
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ String myString1 = o1.toString();
+ String myString2 = o2.toString();
+ myString1 = myString1.toLowerCase();
+ myString2 = myString2.toLowerCase();
+ StringTokenizer st = new StringTokenizer(myString1, "%");
+
+ while (st.hasMoreTokens()) {
+ String part = st.nextToken();
+ int index = myString2.indexOf(part);
+ if (index > -1)
+ {
+ myString2 = myString2.substring(index + part.length());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ return Boolean.TRUE;
+
+ }
+
+ public String toString()
+ {
+ return "caseInsensitiveLike";
+ }
+ }
+
+ static class OperatorContains extends BaseSelector {
+
+ public boolean isRelationalOperator()
+ {
+ return false;
+ }
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ String myString1 = o1.toString();
+ String myString2 = o2.toString();
+ return new Boolean(
+ myString2.indexOf(myString1) > -1 );
+ }
+
+ public String toString()
+ {
+ return "contains";
+ }
+ }
+
+ static class OperatorEqual extends BaseSelector {
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ return new Boolean( doCompare(o1, o2) == 0 );
+ }
+
+ public String toString()
+ {
+ return "=";
+ }
+ }
+
+ static class OperatorGreaterThan extends BaseSelector {
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ return new Boolean( doCompare(o1, o2) > 0 );
+ }
+
+ public String toString()
+ {
+ return ">";
+ }
+ }
+
+ static class OperatorGreaterThanOrEqualTo extends BaseSelector {
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ return new Boolean( doCompare(o1, o2) >= 0 );
+ }
+
+ public String toString()
+ {
+ return new String(" >= ");
+ }
+ }
+
+ static class OperatorLessThan extends BaseSelector {
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ return new Boolean( doCompare(o1, o2) < 0 );
+ }
+
+ public String toString()
+ {
+ return ">";
+ }
+ }
+
+ static class OperatorLessThanOrEqualTo extends BaseSelector {
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ return new Boolean (doCompare(o1, o2) <= 0);
+ }
+
+ public String toString()
+ {
+ return "<=";
+ }
+ }
+
+ static class OperatorLike extends BaseSelector {
+
+ public boolean isRelationalOperator()
+ {
+ return false;
+ }
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ String myString1 = o1.toString();
+ String myString2 = o2.toString();
+ StringTokenizer st = new StringTokenizer(myString1, "%");
+ while (st.hasMoreTokens()) {
+ String part = st.nextToken();
+ int index = myString2.indexOf(part);
+ if (index > -1)
+ {
+ myString2 = myString2.substring(index + part.length());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ return Boolean.TRUE;
+ }
+
+ public String toString()
+ {
+ return "like";
+ }
+ }
+
+ static class OperatorNotEqual extends BaseSelector {
+
+ protected Boolean qualify( Object o1, Object o2 )
+ {
+ return new Boolean(!(o1.equals(o2)));
+ }
+
+ public String toString()
+ {
+ return "!=";
+ }
+ }
+
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver ua) {
+ String cname = (String)ua.decodeObjectForKey("class");
+ if (cname.equals("EOKeyValueQualifier"))
+ return (EOQualifier)EOKeyValueQualifier.decodeWithKeyValueUnarchiver(ua);
+ if (cname.equals("EOAndQualifier"))
+ return (EOQualifier)EOAndQualifier.decodeWithKeyValueUnarchiver(ua);
+ if (cname.equals("EOOrQualifier"))
+ return (EOQualifier)EOOrQualifier.decodeWithKeyValueUnarchiver(ua);
+ if (cname.equals("EONotQualifier"))
+ return (EOQualifier)EONotQualifier.decodeWithKeyValueUnarchiver(ua);
+ return null;
+ }
+
+}
+/*
+ * $Log$
+ * 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.10 2003/08/09 01:22:51 chochos
+ * qualifiers implement EOKeyValueArchiving
+ *
+ * Revision 1.9 2001/11/04 18:29:11 mpowers
+ * Better handling for non-string types used with non-relational operators.
+ *
+ * Revision 1.8 2001/10/31 15:25:14 mpowers
+ * Cleanup of qualifiers.
+ *
+ * Revision 1.7 2001/10/30 22:57:28 mpowers
+ * EOQualifier framework is now working.
+ *
+ * Revision 1.6 2001/10/30 22:16:37 mpowers
+ * Implemented operators as selectors.
+ *
+ * Revision 1.5 2001/09/14 14:21:28 mpowers
+ * Updated javadoc.
+ *
+ * Revision 1.3 2001/09/13 15:25:56 mpowers
+ * Started implementation of the EOQualifier framework.
+ *
+ * Revision 1.2 2001/02/27 03:33:04 mpowers
+ * Initial draft of the key-value qualifier.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:47 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java
new file mode 100644
index 0000000..87769b8
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java
@@ -0,0 +1,42 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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;
+
+/**
+* EOQualifiers that want to perform in-memory
+* evaluation should implement this interface. <br><br>
+*
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EOQualifierEvaluation {
+
+ public boolean evaluateWithObject(Object eo);
+
+}
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2003/08/12 01:42:17 chochos
+ * formally declare this interface
+ *
+ */
+ \ No newline at end of file
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java
new file mode 100644
index 0000000..568b555
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java
@@ -0,0 +1,76 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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;
+
+/**
+* EORelationshipManipulation provides methods for generically
+* adding and removing relationships between objects, handling
+* both one-way and reciprocal relationships.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EORelationshipManipulation
+{
+ /**
+ * Adds the specified object to the relationship on this
+ * object specified by the key. For to-one relationships,
+ * this operation is the same as valueForKey.
+ */
+ void addObjectToPropertyWithKey(
+ Object anObject, String aKey );
+
+ /**
+ * Removes the specified object from the relationship on
+ * this object specified by the key. For to-one relationships,
+ * this operation is the same as takeValueForKey with a null
+ * value.
+ */
+ void removeObjectFromPropertyWithKey(
+ Object anObject, String aKey );
+
+ /**
+ * As addObjectToProperty with key, but also performs the
+ * reciprocal operation on the other side of the relationship.
+ */
+ void addObjectToBothSidesOfRelationshipWithKey(
+ EORelationshipManipulation anObject, String aKey );
+
+ /**
+ * As removeObjectFromPropertyWithKey with key, but also performs the
+ * reciprocal operation on the other side of the relationship.
+ */
+ void removeObjectFromBothSidesOfRelationshipWithKey(
+ EORelationshipManipulation anObject, String aKey );
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2001/11/13 04:13:59 mpowers
+ * Added interfaces needed to begin work on EOCustomObject.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java
new file mode 100644
index 0000000..789b6da
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java
@@ -0,0 +1,406 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSSelector;
+
+/**
+* EOSortOrdering defines a sort key and operation.
+* DisplayGroups use lists of EOSortOrdering to determine
+* how to order their items.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EOSortOrdering implements Serializable, EOKeyValueArchiving
+{
+ /**
+ * Sorts items in ascending order.
+ */
+ public static final
+ NSSelector CompareAscending = new CompareAscendingComparator();
+
+ /**
+ * Sorts items in descending order.
+ */
+ public static final
+ NSSelector CompareDescending = new CompareDescendingComparator();
+
+ /**
+ * Sorts items' string representations in ascending order
+ * in a case insensitive manner.
+ */
+ public static final
+ NSSelector CompareCaseInsensitiveAscending =
+ new CompareCaseInsensitiveAscendingComparator();
+
+ /**
+ * Sorts items' string representations in descending order
+ * in a case insensitive manner.
+ */
+ public static final
+ NSSelector CompareCaseInsensitiveDescending =
+ new CompareCaseInsensitiveDescendingComparator();
+
+ protected String key;
+ protected NSSelector selector;
+
+ /**
+ * Factory-style constructor returns a new EOSortOrdering instance
+ * with the specified key and selector. Neither may be null.
+ */
+ public static EOSortOrdering sortOrderingWithKey(String key, NSSelector selector)
+ {
+ return new EOSortOrdering( key, selector );
+ }
+
+ /**
+ * Constructor creates an EOSortOrdering that uses the
+ * specified key and selector. Neither may be null.
+ */
+ public EOSortOrdering( String aKey, NSSelector aSelector )
+ {
+ key = aKey;
+ selector = aSelector;
+ }
+
+ /**
+ * Constructor creates an EOSortOrdering that uses the
+ * specified key and comparator. Neither may be null.
+ * Not in the spec.
+ */
+ public EOSortOrdering( String aKey, Comparator aComparator )
+ {
+ key = aKey;
+ selector = new NSSelector( aKey, aComparator );
+ }
+
+ /**
+ * Returns the property key.
+ */
+ public String key()
+ {
+ return key;
+ }
+
+ /**
+ * Returns the selector.
+ */
+ public NSSelector selector()
+ {
+ return selector;
+ }
+
+ public String toString()
+ {
+ return "[EOSortOrdering: key='"+key+"' selector='"+selector+"']";
+ }
+
+ public boolean equals( Object anObject )
+ {
+ if ( anObject instanceof EOSortOrdering )
+ {
+ EOSortOrdering x = (EOSortOrdering) anObject;
+ if ( selector().equals( x.selector() ) )
+ {
+ if ( key().equals( x.key() ) )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sorts the specified list in place according to the specified
+ * list of EOSortOrderings. The items will be sorted first by the
+ * first ordering, and items with equal values for that property
+ * will be sorted by the next ordering, and so on.
+ */
+ public static void sortArrayUsingKeyOrderArray(
+ List anObjectList, List aSortOrderingList )
+ {
+ List keys = new ArrayList( aSortOrderingList );
+ Collections.reverse( keys );
+ Iterator it = keys.iterator();
+ EOSortOrdering sortOrdering;
+ while ( it.hasNext() )
+ {
+ sortOrdering = (EOSortOrdering) it.next();
+ Collections.sort( anObjectList,
+ new DelegatingComparator(
+ sortOrdering.key(), sortOrdering.selector() ) );
+ }
+ }
+
+ /**
+ * Sorts the specified list in place according to the specified
+ * list of EOSortOrderings. The items will be sorted first by the
+ * first ordering, and items with equal values for that property
+ * will be sorted by the next ordering, and so on.
+ */
+ public static NSArray sortedArrayUsingKeyOrderArray(
+ List anObjectList, List aSortOrderingList )
+ {
+ NSArray result = new NSMutableArray();
+ result.addAll( anObjectList );
+ sortArrayUsingKeyOrderArray( result, aSortOrderingList );
+ return result;
+ }
+
+ public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) {
+ String k = (String)arch.decodeObjectForKey("key");
+ String sname = (String)arch.decodeObjectForKey("selectorName");
+ NSSelector sel = null;
+ if (sname.equals("compareAscending:"))
+ sel = CompareAscending;
+ else if (sname.equals("compareDescending:"))
+ sel = CompareDescending;
+ else if (sname.equals("compareCaseInsensitiveAscending:"))
+ sel = CompareCaseInsensitiveAscending;
+ else if (sname.equals("compareCaseInsensitiveDescending:"))
+ sel = CompareCaseInsensitiveAscending;
+ else {
+ if (sname.endsWith(":"))
+ sname = sname.substring(0, sname.length()-1);
+ sel = new NSSelector(sname, new Class[]{ Object.class });
+ }
+ return new EOSortOrdering(k, sel);
+ }
+
+ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) {
+ arch.encodeObject("EOSortOrdering", "class");
+ arch.encodeObject(key(), "key");
+ if (selector.equals(CompareAscending))
+ arch.encodeObject("compareAscending:", "selectorName");
+ else if (selector.equals(CompareDescending))
+ arch.encodeObject("compareDescending:", "selectorName");
+ else if (selector.equals(CompareCaseInsensitiveAscending))
+ arch.encodeObject("compareCaseInsensitiveAscending:", "selectorName");
+ else if (selector.equals(CompareCaseInsensitiveAscending))
+ arch.encodeObject("compareCaseInsensitiveDescending:", "selectorName");
+ else
+ arch.encodeObject(selector.name() + ":", "selectorName");
+ }
+
+ private static class CompareAscendingComparator
+ extends NSSelector
+ {
+ public int compare(Object o1, Object o2)
+ {
+ if ( o1 instanceof Comparable )
+ {
+ if ( o2 instanceof Comparable )
+ {
+ return ((Comparable)o1).compareTo( o2 );
+ }
+ }
+
+ // null handling: null is less than any object
+
+ if ( o1 == null )
+ {
+ if ( o2 == null )
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else // o1 != null
+ if ( o2 == null )
+ {
+ return 1;
+ }
+
+ // fall back on string representation comparison
+
+ return o1.toString().compareTo( o2.toString() );
+ }
+
+ public boolean equals(Object obj)
+ {
+ return ( this == obj );
+ }
+ }
+
+ private static class CompareDescendingComparator
+ extends CompareAscendingComparator
+ {
+ public int compare(Object o1, Object o2)
+ {
+ return -1 * super.compare( o1, o2 );
+ }
+ }
+
+ private static class CompareCaseInsensitiveAscendingComparator
+ extends NSSelector
+ {
+ public int compare(Object o1, Object o2)
+ {
+ // null handling: null is less than any object
+
+ if ( o1 == null )
+ {
+ if ( o2 == null )
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else // o1 != null
+ if ( o2 == null )
+ {
+ return 1;
+ }
+
+ return o1.toString().toLowerCase().compareTo(
+ o2.toString().toLowerCase() );
+ }
+
+ public boolean equals(Object obj)
+ {
+ return ( this == obj );
+ }
+ }
+
+ private static class CompareCaseInsensitiveDescendingComparator
+ extends CompareCaseInsensitiveAscendingComparator
+ {
+ public int compare(Object o1, Object o2)
+ {
+ return -1 * super.compare( o1, o2 );
+ }
+ }
+
+ private static class DelegatingComparator implements Comparator
+ {
+ private String key;
+ private Comparator comparator;
+
+ public DelegatingComparator( String aKey, Comparator aComparator )
+ {
+ key = aKey;
+ comparator = aComparator;
+ }
+
+ public int compare(Object o1, Object o2)
+ {
+ Object v1, v2;
+ if ( o1 instanceof EOKeyValueCoding )
+ {
+ v1 = ((EOKeyValueCoding)o1).valueForKey( key );
+ }
+ else
+ {
+ v1 = EOKeyValueCodingSupport.valueForKey( o1, key );
+ }
+ if ( o2 instanceof EOKeyValueCoding )
+ {
+ v2 = ((EOKeyValueCoding)o2).valueForKey( key );
+ }
+ else
+ {
+ v2 = EOKeyValueCodingSupport.valueForKey( o2, key );
+ }
+ return comparator.compare( v1, v2 );
+ }
+
+ public boolean equals(Object obj)
+ {
+ return ( this == obj );
+ }
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.14 2003/08/11 19:39:52 chochos
+ * now encodes/decodes correctly.
+ *
+ * Revision 1.13 2003/08/09 01:29:56 chochos
+ * implements EOKeyValueArchiving
+ *
+ * Revision 1.12 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.11 2003/02/07 20:23:23 mpowers
+ * Provided backwards compatibility for comparators.
+ *
+ * Revision 1.10 2003/01/18 23:46:58 mpowers
+ * EOSortOrdering is now correctly using NSSelectors.
+ *
+ * Revision 1.9 2003/01/16 22:47:30 mpowers
+ * Compatibility changes to support compiling woextensions source.
+ * (34 out of 56 classes compile!)
+ *
+ * Revision 1.8 2002/03/01 20:23:27 mpowers
+ * Implemented equals.
+ *
+ * Revision 1.7 2002/02/06 21:15:04 mpowers
+ * Added null handling to CompareCaseInsensitiveAscendingComparator.
+ *
+ * Revision 1.6 2001/12/01 23:51:24 mpowers
+ * Made serializable.
+ *
+ * Revision 1.5 2001/03/29 03:29:49 mpowers
+ * Now using KeyValueCoding and Support instead of Introspector.
+ *
+ * Revision 1.4 2001/01/24 22:15:52 mpowers
+ * Fixed npe when comparing nulls.
+ *
+ * Revision 1.3 2001/01/12 19:11:56 mpowers
+ * Fixed table column click sorting.
+ *
+ * Revision 1.2 2001/01/12 17:23:46 mpowers
+ * Inner classes are now private.
+ *
+ * Revision 1.1 2001/01/11 20:34:26 mpowers
+ * Implemented EOSortOrdering and added support in framework.
+ * Added header-click to sort table columns.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java
new file mode 100644
index 0000000..44ba855
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java
@@ -0,0 +1,219 @@
+/*
+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.net.InetAddress;
+
+/**
+* EOTemporaryGlobalID is a network-wide unique key.
+* This is used by EOEditingContext to construct temporary
+* ids when new objects are created. <br><br>
+*
+* The specified format of the key is a byte array:
+* &lt; Sequence [2], ProcessID [2], Time [4], IP Addr [4] &gt;,
+* but because java does not allow access to the process id,
+* the timestamp of when this class is first loaded is used
+* to simulate a process id.
+*
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public class EOTemporaryGlobalID
+ extends EOGlobalID
+{
+ /**
+ * Holds the length in bytes of the key that is generated.
+ */
+ public static final int UniqueBinaryKeyLength = 12;
+
+ private static int sequence;
+ private static byte[] processid; // 2 bytes
+ private static byte[] ipaddr; // 4 bytes
+
+ static // static initializer
+ {
+ // init sequence
+ sequence = 0;
+
+ // init processid
+ processid = new byte[2];
+ long time = System.currentTimeMillis();
+ processid[1] = (byte) time;
+ time = time >> 8;
+ processid[0] = (byte) time;
+
+ // init ipaddr
+ ipaddr = new byte[4];
+ try
+ {
+ ipaddr = InetAddress.getLocalHost().getAddress();
+ }
+ catch ( Exception exc )
+ {
+ // could not obtain ip address - use pid twice
+ ipaddr[0] = processid[0];
+ ipaddr[1] = processid[1];
+ ipaddr[2] = processid[0];
+ ipaddr[3] = processid[1];
+ }
+ }
+
+ private byte[] key;
+ private int hashCode;
+
+ /**
+ * Generates a new id with a unique key.
+ */
+ public EOTemporaryGlobalID()
+ {
+ key = new byte[UniqueBinaryKeyLength];
+
+ // init sequence (important byte first)
+ key[0] = (byte) ( sequence );
+ key[1] = (byte) ( sequence >> 8 );
+ sequence++;
+
+ // populate pid (important byte first)
+ key[2] = processid[1];
+ key[3] = processid[0];
+
+ // init time (important byte first)
+ long time = System.currentTimeMillis();
+ key[4] = (byte) time;
+ time = time >> 8;
+ key[5] = (byte) time;
+ time = time >> 8;
+ key[6] = (byte) time;
+ time = time >> 8;
+ key[7] = (byte) time;
+
+ // populate ipaddr
+ key[8] = ipaddr[0];
+ key[9] = ipaddr[1];
+ key[10] = ipaddr[2];
+ key[11] = ipaddr[3];
+
+ // use string's hash code
+ hashCode = new String( key ).hashCode();
+ }
+
+ /**
+ * Private constructor for cloning.
+ */
+ private EOTemporaryGlobalID( byte[] aKey )
+ {
+// key = aKey; // this might be faster
+
+ // make copy of key - might be safer
+ key = new byte[ UniqueBinaryKeyLength ];
+ for ( int i = 0; i < UniqueBinaryKeyLength; i++ )
+ {
+ key[i] = aKey[i];
+ }
+
+ // use string's hash code
+ hashCode = new String( aKey ).hashCode();
+ }
+
+ /**
+ * Returns true.
+ */
+ public boolean isTemporary()
+ {
+ return true;
+ }
+
+ /**
+ * Returns whether the keys are equal.
+ */
+ public boolean equals( Object anObject )
+ {
+ if ( ! ( anObject instanceof EOTemporaryGlobalID ) )
+ return false;
+
+ byte[] otherKey = ((EOTemporaryGlobalID)anObject).key;
+
+ for ( int i = 0; i < UniqueBinaryKeyLength; i++ )
+ {
+ if ( key[i] != otherKey[i] ) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a copy of this object.
+ */
+ public Object clone()
+ {
+ // faster than super.clone()
+ return new EOTemporaryGlobalID( key );
+ }
+
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ /**
+ * Returns a string representation of this key.
+ * This is a 24-character string with each pair
+ * of characters holding a hexadecimal value that
+ * is 128 more than the value of the corresponding
+ * byte (to account for two's complement).
+ */
+ public String toString()
+ {
+ String hex;
+ StringBuffer buffer = new StringBuffer();
+ for ( int i = 0; i < key.length; i++ )
+ {
+ // get string: adjust for two's complement
+ hex = Integer.toHexString( key[i]+128 );
+ // pad with zero so we take two characters
+ if ( hex.length() == 1 ) hex = "0" + hex;
+ // append hex code
+ buffer.append( hex );
+ }
+ return buffer.toString();
+ }
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.4 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.3 2001/02/15 21:13:30 mpowers
+ * First draft implementation is complete. Now on to debugging.
+ *
+ * Revision 1.2 2001/02/14 23:03:02 mpowers
+ * A near-complete first draft of EOEditingContext.
+ *
+ * Revision 1.1 2001/02/13 23:24:29 mpowers
+ * Implementing more of editing context.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java
new file mode 100644
index 0000000..a0aa4db
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java
@@ -0,0 +1,72 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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;
+
+/**
+* EOValidation provides methods for validating a operation
+* on an object as a whole, rather than on an individual property.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface EOValidation
+{
+ /**
+ * Validates this object for delete.
+ * Throws an exception if this object cannot be deleted.
+ */
+ void validateForDelete();
+
+ /**
+ * Validates this object for insertion into the external store.
+ * Throws an exception if this object cannot be inserted.
+ * Validations here should be specific to insertion.
+ * Implementations may call validateForSave().
+ */
+ void validateForInsert();
+
+ /**
+ * Validates this object for a commit to the external store.
+ * Throws an exception if this object cannot be committed.
+ * Validations here are not specific to either inserts or updates.
+ */
+ void validateForSave();
+
+ /**
+ * Validates this object for update to the external store.
+ * Throws an exception if this object cannot be updated.
+ * Validations here should be specific to updates.
+ * Implementations may call validateForSave().
+ */
+ void validateForUpdate();
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2001/11/13 04:13:59 mpowers
+ * Added interfaces needed to begin work on EOCustomObject.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java
new file mode 100644
index 0000000..cc91fd7
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java
@@ -0,0 +1,79 @@
+/*
+ Wotonomy: OpenStep design patterns for pure Java applications.
+ Copyright (C) 2001 Michael Powers
+
+ 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;
+
+/**
+*
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public class EOVectorKeyGlobalID extends EOKeyGlobalID {
+
+ protected Object[] _keyValues;
+
+ public EOVectorKeyGlobalID(String entityName, Object[] values) {
+ super(entityName, 0);
+ _keyValues = new Object[values.length];
+ for (int i = 0; i < values.length; i++) {
+ _keyValues[i] = values[i];
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyGlobalID#keyValuesNoCopy()
+ */
+ public Object[] _keyValuesNoCopy() {
+ return _keyValues;
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyGlobalID#_keyValues()
+ */
+ public Object[] keyValues() {
+ Object[] v = new Object[_keyValues.length];
+ for (int i = 0; i < _keyValues.length; i++) {
+ v[i] = _keyValues[i];
+ }
+ return v;
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOKeyGlobalID#keyCount()
+ */
+ public int keyCount() {
+ return _keyValues.length;
+ }
+
+ /* (non-Javadoc)
+ * @see net.wotonomy.control.EOGlobalID#isTemporary()
+ */
+ public boolean isTemporary() {
+ return false;
+ }
+
+}
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.2 2003/08/19 01:59:01 chochos
+ * Added the wotonomy headers
+ *
+ */
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java
new file mode 100644
index 0000000..ee40f23
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java
@@ -0,0 +1,283 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.List;
+import java.util.Map;
+
+// swing dependency for undo manager
+//import javax.swing.undo.UndoManager;
+
+/**
+* EditingContext provides transactional support for
+* fetching, editing, and committing changes made on a
+* collection of objects to a parent object store.
+* This subclasses EOEditingContext to provide
+* java-friendly conveniences.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class EditingContext extends EOEditingContext
+{
+ /**
+ * Default constructor creates a new editing context
+ * that uses the default object store. If the default
+ * object store has not been set, an exception is thrown.
+ */
+ public EditingContext()
+ {
+ this( defaultParentObjectStore() );
+ }
+
+ /**
+ * Creates a new editing context that uses the specified
+ * object store as its parent object store.
+ */
+ public EditingContext( EOObjectStore anObjectStore )
+ {
+ super( anObjectStore );
+ }
+
+ /**
+ * Returns a List of objects associated with the object
+ * with the specified id for the specified property
+ * relationship, or may return a placeholder array that
+ * will defer the fetch until needed (aka an array fault).
+ * All objects must be registered in the specified editing context.
+ * This implementation calls to its parent object store's
+ * implementation if the requested source object is not
+ * registered in this editing context.
+ * The specified relationship key must produce a result of
+ * type Collection for the source object or an exception is thrown.
+ */
+ public List getArrayFaultWithSourceGlobalID (
+ EOGlobalID aGlobalID,
+ String aRelationshipKey,
+ EOEditingContext aContext )
+ {
+ return arrayFaultWithSourceGlobalID(
+ aGlobalID, aRelationshipKey, aContext );
+ }
+
+ /**
+ * Returns a snapshot of the specified object as it
+ * existed when it was last read or committed to the
+ * parent object store.
+ */
+ public Map getCommittedSnapshotForObject (
+ Object anObject )
+ {
+ return committedSnapshotForObject( anObject );
+ }
+
+ /**
+ * Returns a snapshot of the specified object as it
+ * existed before the edits triggered by the current
+ * event loop were processed.
+ */
+ public Map getCurrentEventSnapshotForObject (
+ Object anObject )
+ {
+ return currentEventSnapshotForObject( anObject );
+ }
+
+ /**
+ * Returns the delegate for this editing context,
+ * or null if no delegate has been set.
+ */
+ public Object getDelegate ()
+ {
+ return delegate();
+ }
+
+ /**
+ * Returns a List of all objects marked as deleted
+ * in this editing context.
+ */
+ public List getDeletedObjects ()
+ {
+ return deletedObjects();
+ }
+
+ /**
+ * Returns a List of registered editors of this
+ * editing context.
+ */
+ public List getEditors()
+ {
+ return editors();
+ }
+
+ /**
+ * Returns the object for the specified id.
+ * If the object's data has not been fetched,
+ * it will be fetched when needed.
+ */
+ public Object getFaultForGlobalID (
+ EOGlobalID aGlobalID )
+ {
+ return faultForGlobalID( aGlobalID, this );
+ }
+
+ /**
+ * Returns a fault representing an object of
+ * the specified entity type with values from
+ * the specified dictionary.
+ */
+ public Object getFaultForRawRow (
+ Map aDictionary,
+ String anEntityName )
+ {
+ return faultForRawRow( aDictionary, anEntityName );
+ }
+
+ /**
+ * Returns the fetch timestamp for this editing context.
+ */
+ public double getFetchTimestamp()
+ {
+ return fetchTimestamp();
+ }
+
+ /**
+ * Returns the id for the specified object, or null
+ * if the object is not registered in this context.
+ */
+ public EOGlobalID getGlobalIDForObject (
+ Object anObject )
+ {
+ return globalIDForObject( anObject );
+ }
+
+ /**
+ * Returns a List of the objects that have been
+ * inserted into this editing context.
+ */
+ public List getInsertedObjects ()
+ {
+ return insertedObjects();
+ }
+
+ /**
+ * Returns the message handler for this editing context,
+ * or null if no message handler has been set.
+ */
+ public Object getMessageHandler ()
+ {
+ return messageHandler();
+ }
+
+ /**
+ * Returns the object registered in this editing context
+ * for the specified id, or null if that id is not
+ * registered.
+ */
+ public Object getObjectForGlobalID (
+ EOGlobalID aGlobalID )
+ {
+ return objectForGlobalID( aGlobalID );
+ }
+
+ /**
+ * Returns a List of objects the meet the criteria of
+ * the supplied specification.
+ */
+ public List getObjectsWithFetchSpecification (
+ EOFetchSpecification aFetchSpec )
+ {
+ return objectsWithFetchSpecification( aFetchSpec );
+ }
+
+ /**
+ * Returns the parent object store for this editing context.
+ * The result will not be null.
+ */
+ public EOObjectStore getParentObjectStore ()
+ {
+ return parentObjectStore();
+ }
+
+ /**
+ * Returns a List of all objects registered in this
+ * editing context.
+ */
+ public List geRegisteredObjects ()
+ {
+ return registeredObjects();
+ }
+
+ /**
+ * Returns the root object store, which is the parent
+ * of all parent object stores of this editing context.
+ */
+ public EOObjectStore getRootObjectStore ()
+ {
+ return rootObjectStore();
+ }
+
+ /**
+ * Returns a list of all objects marked as modified,
+ * but not inserted or deleted, in this editing context.
+ */
+ public List getUpdatedObjects ()
+ {
+ return updatedObjects();
+ }
+
+ // static methods
+
+ public static double getDefaultFetchTimestampLag()
+ {
+ return defaultFetchTimestampLag();
+ }
+
+ /**
+ * Returns the default parent object store for all
+ * object stores created with the parameterless
+ * constructor.
+ */
+ public static EOObjectStore getDefaultParentObjectStore()
+ {
+ return defaultParentObjectStore();
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.2 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.1 2002/03/26 21:46:36 mpowers
+ * Contributing EditingContext as a java-friendly convenience.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java
new file mode 100644
index 0000000..1aa2147
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java
@@ -0,0 +1,740 @@
+/*
+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.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.internal.Duplicator;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* KeyValueCodingUtilities implements what
+* EOKeyValueCodingSupport leaves out. Importantly,
+* this class implements the deep clone and deep copy
+* operations that are essential to the functioning of
+* nested editing contexts.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 900 $
+*/
+public class KeyValueCodingUtilities
+{
+ /**
+ * Returns a Map of the specified keys to their values,
+ * each of which is obtained by calling valueForKey
+ * on the specified object if it implements EOKeyValueCoding,
+ * and otherwise falling back on EOKeyValueCodingSupport.
+ * Null values must be represented by NSNull.nullValue().
+ */
+ static public NSDictionary valuesForKeys(
+ Object anObject, List aKeyList )
+ {
+ return valuesForKeys( anObject, aKeyList, false );
+ }
+
+ /**
+ * Returns a Map of the specified keys to their values,
+ * each of which is obtained by calling storedValueForKey
+ * on the specified object if it implements EOKeyValueCoding,
+ * and otherwise falling back on EOKeyValueCodingSupport.
+ * Null values must be represented by NSNull.nullValue().
+ */
+ static public NSDictionary storedValuesForKeys(
+ Object anObject, List aKeyList )
+ {
+ return valuesForKeys( anObject, aKeyList, true );
+ }
+
+ /**
+ * Called by valuesForKeys and storedValuesForKeys.
+ * This uses storedValueForKey if isStored is true,
+ * otherwise uses valueForKey.
+ */
+ static private NSDictionary valuesForKeys(
+ Object anObject, List aKeyList, boolean isStored )
+ {
+ EOKeyValueCoding coding;
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ coding = (EOKeyValueCoding) anObject;
+ }
+ else
+ {
+ coding = null;
+ }
+
+ String key;
+ Object value;
+ NSMutableDictionary result = new NSMutableDictionary();
+ Iterator it = aKeyList.iterator();
+ while ( it.hasNext() )
+ {
+ //TODO: get rid of this try/catch - exceptions should be fatal (?)
+ try
+ {
+ key = it.next().toString();
+ if ( coding != null )
+ {
+ if ( isStored )
+ value = coding.storedValueForKey( key );
+ else
+ value = coding.valueForKey( key );
+ }
+ else
+ {
+ if ( isStored )
+ value = EOKeyValueCodingSupport.storedValueForKey( anObject, key );
+ else
+ value = EOKeyValueCodingSupport.valueForKey( anObject, key );
+ }
+ if ( value == null )
+ {
+ value = EONullValue.nullValue();
+ }
+ result.setObjectForKey( value, key );
+ }
+ catch ( RuntimeException exc )
+ {
+ System.out.println(
+ "KeyValueCodingUtilities.valuesForKeys: "
+ + isStored + " : " + exc );
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Takes the keys from the specified Map as properties
+ * and applies the corresponding values, each of which
+ * might be set by calling takeValueForKey on the
+ * specified object if it implements EOKeyValueCoding,
+ * and otherwise falling back on EOKeyValueCodingSupport.
+ * Null values must be represented by NSNull.nullValue().
+ */
+ static public void takeValuesFromDictionary(
+ Object anObject, Map aMap )
+ {
+ takeStoredValuesFromDictionary( anObject, aMap, false );
+ }
+
+ /**
+ * Takes the keys from the specified Map as properties
+ * and applies the corresponding values, each of which
+ * might be set by calling takeStoredValueForKey on the
+ * specified object if it implements EOKeyValueCoding,
+ * and otherwise falling back on EOKeyValueCodingSupport.
+ * Null values must be represented by NSNull.nullValue().
+ */
+ static public void takeStoredValuesFromDictionary(
+ Object anObject, Map aMap )
+ {
+ takeStoredValuesFromDictionary( anObject, aMap, true );
+ }
+
+ /**
+ * Called by takeValuesFromDictionary and takeStoredValuesFromDictionary.
+ * This uses takeStoredValueForKey if isStored is true,
+ * otherwise uses takeValueForKey.
+ */
+ static private void takeStoredValuesFromDictionary(
+ Object anObject, Map aMap, boolean isStored )
+ {
+ EOKeyValueCoding coding;
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ coding = (EOKeyValueCoding) anObject;
+ }
+ else
+ {
+ coding = null;
+ }
+
+ String key;
+ Object value;
+ NSMutableDictionary result = new NSMutableDictionary();
+ Iterator it = aMap.keySet().iterator();
+ while ( it.hasNext() )
+ {
+ //TODO: get rid of this try/catch - exceptions should be fatal (?)
+ try
+ {
+ key = it.next().toString();
+ value = aMap.get( key );
+ if ( value instanceof EONullValue )
+ // can't use == nullValue() because of cloning/serialization
+ {
+ value = null;
+ }
+ if ( coding != null )
+ {
+ if ( isStored )
+ coding.takeStoredValueForKey( value, key );
+ else
+ coding.takeValueForKey( value, key );
+ }
+ else
+ {
+ if ( isStored )
+ EOKeyValueCodingSupport.takeStoredValueForKey(
+ anObject, value, key );
+ else
+ EOKeyValueCodingSupport.takeValueForKey(
+ anObject, value, key );
+ }
+ }
+ catch ( WotonomyException exc )
+ {
+ System.out.println(
+ "KeyValueCodingUtilities.takeStoredValuesFromDictionary: "
+ + isStored + " : " + exc );
+ }
+ }
+ }
+
+ /**
+ * Creates a deep clone of the specified object.
+ * (Object.clone() only creates a shallow clone.)
+ * Returns null if operation fails.
+ */
+ static public Object clone( Object aSource )
+ {
+ return Duplicator.deepClone( aSource );
+ }
+
+ /**
+ * Creates a deep clone of the specified object,
+ * registered in the specified source editing context,
+ * transposing it into the specified destination
+ * editing context.
+ * Returns null if operation fails.
+ */
+ static public Object clone(
+ EOEditingContext aSourceContext, Object aSource,
+ EOEditingContext aDestinationContext )
+ {
+ return clone( aSourceContext, aSource, aDestinationContext, aSource );
+ }
+
+ /**
+ * Called by clone and copy.
+ * The specified root object will not be replaced
+ * by an object in the destination editing context:
+ * this should be the same as the source object for
+ * cloning, but should be null for copying.
+ * Returns null if operation fails.
+ */
+ static private Object clone(
+ EOEditingContext aSourceContext, Object aSource,
+ EOEditingContext aDestinationContext,
+ Object aRootObject )
+ {
+
+//System.out.println();
+//System.out.println( "clone: " + aSourceContext );
+//System.out.println( " : " + aSource );
+//System.out.println( " : " + aDestinationContext );
+//System.out.println();
+
+ // the only known way to deep copy in
+ // java without native code is serialization
+
+ return thaw(
+ freeze( aSource, aSourceContext, aRootObject, true ),
+ aDestinationContext, true );
+ }
+
+ /**
+ * Serializes an object to a byte array containing
+ * GlobalIDMarkers in place of references to other objects
+ * registered in the specified context.
+ * The specified root object will be serialized,
+ * even if it is registered in the specified context:
+ * this is typically the root object you're trying to
+ * serialize.
+ * Package access, as this method is used by editing
+ * context for snapshots.
+ */
+ static public byte[] freeze(
+ Object anObject, EOEditingContext aContext, Object aRootObject, boolean transpose )
+ {
+ try
+ {
+//long t = System.currentTimeMillis();
+ ByteArrayOutputStream byteOutput =
+ new ByteArrayOutputStream();// CloneBufferSize );
+ ObjectOutputStream objectOutput;
+ if ( transpose )
+ {
+ objectOutput =
+ new TransposingContextObjectOutputStream(
+ byteOutput, aContext, aRootObject );
+ }
+ else
+ {
+ objectOutput =
+ new ContextObjectOutputStream(
+ byteOutput, aContext );
+ }
+
+ objectOutput.writeObject( anObject );
+ objectOutput.flush();
+ objectOutput.close();
+
+ return byteOutput.toByteArray();
+
+// profiling
+/*
+byte[] result = byteOutput.toByteArray();
+long size = result.length;
+long time = ( System.currentTimeMillis() - t );
+maxSize = Math.max( size, maxSize );
+minSize = Math.min( size, minSize );
+totSize += size;
+maxTime = Math.max( time, maxTime );
+minTime = Math.min( time, minTime );
+totTime += time;
+nTime++;
+System.out.println( "freeze: size = [ " + size + " : " + minSize + " : " + ( (float)totSize / (float)nTime ) + " : " + maxSize
++ " ] time = [ " + time + " : " + minTime + " : " + ( (float)totTime / (float)nTime ) + " : " + maxTime + " ]" );
+return result;
+*/
+// end profiling
+
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+ }
+
+//static long maxTime, minTime, totTime, nTime, maxSize, minSize, totSize;
+//static long maxTimeThaw, minTimeThaw, totTimeThaw, nTimeThaw;
+
+ /**
+ * De-serializes an object from the specified byte
+ * array, replacing GlobalIDMarkers with reference
+ * to objects registered in the specified editing
+ * context.
+ * Package access, as this method is used by editing
+ * context for snapshots.
+ */
+ static public Object thaw(
+ byte[] aByteArray, EOEditingContext aContext, boolean transpose )
+ {
+ return thaw( aByteArray, aContext, null, transpose );
+ }
+
+ /**
+ * De-serializes an object from the specified byte
+ * array, replacing GlobalIDMarkers with reference
+ * to objects registered in the specified editing
+ * context.
+ * Package access, as this method is used by editing
+ * context for snapshots.
+ */
+ static public Object thaw(
+ byte[] aByteArray, EOEditingContext aContext, ClassLoader aLoader, boolean transpose )
+ {
+ try
+ {
+//long t = System.currentTimeMillis();
+ ByteArrayInputStream byteInput =
+ new ByteArrayInputStream( aByteArray );
+ ObjectInputStream objectInput;
+ if ( transpose )
+ {
+ objectInput =
+ new TransposingContextObjectInputStream(
+ byteInput, aContext, aLoader );
+ }
+ else
+ {
+ objectInput =
+ new ContextObjectInputStream(
+ byteInput, aContext, aLoader );
+ }
+
+ return objectInput.readObject();
+// profiling
+/*
+Object result = objectInput.readObject();
+long timeThaw = ( System.currentTimeMillis() - t );
+maxTimeThaw = Math.max( timeThaw, maxTimeThaw );
+minTimeThaw = Math.min( timeThaw, minTimeThaw );
+totTimeThaw += timeThaw;
+nTimeThaw++;
+System.out.println( "thaw: size = " + aByteArray.length + ", time = [ " + timeThaw + " : " + minTimeThaw + " : " + ( (float)totTimeThaw / (float)nTimeThaw ) + " : " + maxTimeThaw + " ]" );
+return result;
+*/
+// end profiling
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+ }
+
+ /**
+ * Copies values from one object registered in the
+ * specified origin context to the specified destination
+ * object
+ * The values themselves are cloned, so this is a deep copy.
+ * Returns the destination object, or throws exception
+ * if operation fails.
+ */
+ static public Object copy( Object aSource, Object aDestination )
+ {
+ NSDictionary values = (NSDictionary)
+ clone( valuesForKeys( aSource,
+ EOClassDescription.classDescriptionForClass(
+ aSource.getClass() ).attributeKeys() ) );
+
+ takeStoredValuesFromDictionary( aDestination, values );
+ return aDestination;
+ }
+
+ /**
+ * Copies values from one object registered in the
+ * specified origin context to the specified destination
+ * object
+ * The values themselves are cloned, so this is a deep copy.
+ * Returns the destination object, or throws exception
+ * if operation fails.
+ */
+ static public Object copy(
+ EOEditingContext aSourceContext, Object aSource,
+ EOEditingContext aDestinationContext, Object aDestination )
+ {
+ // get all keys for this object
+ EOClassDescription classDesc =
+ EOClassDescription.classDescriptionForClass( aSource.getClass() );
+ List keys = new LinkedList();
+ keys.addAll( classDesc.attributeKeys() );
+ keys.addAll( classDesc.toOneRelationshipKeys() );
+ keys.addAll( classDesc.toManyRelationshipKeys() );
+
+ // transpose all objects registered in source context
+ NSDictionary values = storedValuesForKeys( aSource, keys );
+ values = (NSDictionary)
+ clone( aSourceContext, values, aDestinationContext, null );
+
+ // apply to destination object
+ takeStoredValuesFromDictionary( aDestination, values );
+ return aDestination;
+ }
+
+ // inner classes
+
+ /**
+ * An ObjectOutputStream that serializes objects with references
+ * to an editing context. The specified context will not be
+ * serialized but referenced, so that a ContextObjectInputStream
+ * can replace the reference with another editing context.
+ */
+ static private class ContextObjectOutputStream extends ObjectOutputStream
+ {
+ private EditingContextMarker marker = new EditingContextMarker();
+ protected EOEditingContext editingContext;
+
+ /**
+ * Specifies the output stream to wrap,
+ * and the source context that should be
+ * referenced but not serialized.
+ */
+ public ContextObjectOutputStream(
+ OutputStream anOutputStream,
+ EOEditingContext aContext )
+ throws IOException
+ {
+ super( anOutputStream );
+ editingContext = aContext;
+ try
+ {
+ enableReplaceObject(true);
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ }
+ }
+
+ protected Object replaceObject(Object anObject) throws IOException
+ {
+// if ( anObject == editingContext ) return marker;
+//FIXME: this should be more strict as above
+ if ( anObject instanceof EOEditingContext ) return marker;
+ return anObject;
+ }
+
+ }
+
+ /**
+ * A ContextObjectOutputStream that replaces any objects registered
+ * in the source editing context with markers to be used in
+ * ContextObjectInputStream.
+ */
+ static private class TransposingContextObjectOutputStream
+ extends ContextObjectOutputStream
+ {
+ protected Object rootObject;
+
+ /**
+ * Specifies the output stream to wrap,
+ * the source context containing objects that
+ * should be replaced if found,
+ * and the object which should not be re-registered,
+ * which is typically the object being cloned, but
+ * may be null.
+ */
+ public TransposingContextObjectOutputStream(
+ OutputStream anOutputStream,
+ EOEditingContext aContext,
+ Object anObject )
+ throws IOException
+ {
+ super( anOutputStream, aContext );
+ rootObject = anObject;
+ }
+
+ protected Object replaceObject(Object anObject) throws IOException
+ {
+ if ( anObject == rootObject ) return anObject;
+ if ( editingContext != null )
+ {
+ EOGlobalID id = editingContext.globalIDForObject( anObject );
+ if ( id != null )
+ {
+ Object result = new GlobalIDMarker( id );
+ //System.out.println( "KeyValueCodingUtilities.replaceObject: returning: " + result );
+ return result;
+ }
+ }
+ return super.replaceObject( anObject );
+ }
+
+ }
+
+ /**
+ * A marker class so references to objects registered in editing
+ * contexts get transposed rather than cloned.
+ */
+ static private class GlobalIDMarker implements Serializable
+ {
+ private EOGlobalID id;
+
+ public GlobalIDMarker( EOGlobalID anID )
+ {
+ id = anID;
+ }
+
+ public EOGlobalID getID()
+ {
+ return id;
+ }
+
+ public String toString()
+ {
+ return "[GlobalIDMarker:"+id+"]";
+ }
+ }
+
+ /**
+ * A marker class so references an object's editing context
+ * gets transposed rather than cloned.
+ */
+ static private class EditingContextMarker implements Serializable
+ {
+ // just a marker class - no implementation necessary
+ }
+
+ /**
+ * An ObjectInputStream that replaces any markers from
+ * ContextObjectOutputStream with objects registered
+ * in the destination editing context.
+ */
+ static private class ContextObjectInputStream extends ObjectInputStream
+ {
+ protected EOEditingContext editingContext;
+ protected ClassLoader classLoader;
+
+ /**
+ * Specifies the output stream to wrap,
+ * the source context containing objects that
+ * should be to replace any markers.
+ * The class loader may be null.
+ */
+ public ContextObjectInputStream(
+ InputStream anInputStream,
+ EOEditingContext aContext,
+ ClassLoader aClassLoader )
+ throws IOException
+ {
+ super( anInputStream );
+ editingContext = aContext;
+ classLoader = aClassLoader;
+ if ( classLoader == null )
+ {
+ classLoader =
+ KeyValueCodingUtilities.class.getClassLoader();
+ }
+ try
+ {
+ enableResolveObject(true);
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ }
+ }
+
+ protected Object resolveObject(Object anObject) throws IOException
+ {
+ if ( anObject instanceof EditingContextMarker )
+ {
+ return editingContext;
+ }
+ return anObject;
+ }
+
+ protected Class resolveClass(ObjectStreamClass v)
+ throws IOException, ClassNotFoundException
+ {
+ return classLoader.loadClass( v.getName() );
+ }
+ }
+
+ /**
+ * A ContextObjectInputStream that replaces any markers from
+ * TransposingContextObjectOutputStream with objects registered
+ * in the destination editing context.
+ */
+ static private class TransposingContextObjectInputStream
+ extends ContextObjectInputStream
+ {
+ /**
+ * Specifies the output stream to wrap,
+ * the source context containing objects that
+ * should be to replace any markers.
+ */
+ public TransposingContextObjectInputStream(
+ InputStream anInputStream,
+ EOEditingContext aContext,
+ ClassLoader aClassLoader )
+ throws IOException
+ {
+ super( anInputStream, aContext, aClassLoader );
+ }
+
+ protected Object resolveObject(Object anObject) throws IOException
+ {
+ if ( anObject instanceof GlobalIDMarker )
+ {
+ return editingContext.faultForGlobalID(
+ ((GlobalIDMarker)anObject).getID(), editingContext );
+ }
+ return super.resolveObject( anObject );
+ }
+ }
+
+}
+
+/*
+ * $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.15 2003/01/21 22:30:10 mpowers
+ * thaw() now allows you to pass in a class loader.
+ *
+ * Revision 1.14 2002/05/15 13:46:35 mpowers
+ * Exposed freeze and thaw as public.
+ *
+ * Revision 1.13 2001/08/22 19:25:13 mpowers
+ * Added (and commented out) profiling code for freeze.
+ *
+ * Revision 1.12 2001/05/06 18:27:10 mpowers
+ * More broadly catching editing contexts for now.
+ *
+ * Revision 1.11 2001/05/05 13:18:49 mpowers
+ * Fixed: transposing output stream was not returning the object to replace.
+ *
+ * Revision 1.10 2001/05/04 16:57:56 mpowers
+ * Now correctly transposing references to editing contexts when
+ * cloning/copying between editing contexts.
+ *
+ * Revision 1.9 2001/05/04 14:42:58 mpowers
+ * Now getting stored values in KeyValueCoding.
+ * MasterDetail now marks dirty based on whether it's an attribute
+ * or relation.
+ * Implemented editing context marker.
+ *
+ * Revision 1.8 2001/05/02 15:47:40 mpowers
+ * Fixed the pernicious problem with reverts: recordObject was recording
+ * a snapshot of the clone before the transposition-copy happened,
+ * so the revert object would lose all of its transposed relationships.
+ *
+ * Revision 1.7 2001/04/30 12:33:17 mpowers
+ * Fixed problem with use of EONullValue.nullValue(), which can't be used
+ * when we're serializably duplicating objects.
+ *
+ * Revision 1.6 2001/04/30 02:14:25 mpowers
+ * Copying should call takeStoredValueForKeys.
+ *
+ * Revision 1.5 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.4 2001/04/29 02:29:31 mpowers
+ * Debugging relationship faulting.
+ *
+ * Revision 1.3 2001/04/28 16:18:44 mpowers
+ * Implementing relationships.
+ *
+ * Revision 1.2 2001/04/28 14:12:23 mpowers
+ * Refactored cloning/copying into KeyValueCodingUtilities.
+ *
+ * Revision 1.1 2001/04/27 23:41:12 mpowers
+ * Contributing file for KeyValueCodingUtilities.
+ *
+ *
+ */
+
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java
new file mode 100644
index 0000000..19d39ff
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java
@@ -0,0 +1,346 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSRange;
+
+/**
+* A package class that extends NSMutableArray but makes use
+* of the fact that wotonomy's implementation extends ArrayList
+* to intercept insertions and deletion and register and
+* unregister objects for change notifications as appropriate.
+* Since we can't be sure of ArrayList's implementation, we're
+* forced to override each and every add and remove method,
+* some of which probably call each other. However,
+* EOObserverCenter will only register us once per object.
+*/
+class ObservableArray extends NSMutableArray
+{
+ EOObserving observer;
+
+ ObservableArray( EOObserving anObserver )
+ {
+ observer = anObserver;
+ }
+
+ /**
+ * Removes the last object from the array.
+ */
+ public void removeLastObject ()
+ {
+ remove( count() - 1 );
+ }
+
+ /**
+ * Removes the object at the specified index.
+ */
+ public void removeObjectAtIndex (int index)
+ {
+ remove( index );
+ }
+
+ /**
+ * Adds all objects in the specified collection.
+ */
+ public void addObjectsFromArray (Collection aCollection)
+ {
+ addAll( aCollection );
+ }
+
+ /**
+ * Removes all objects from the array.
+ */
+ public void removeAllObjects ()
+ {
+ clear();
+ }
+
+ /**
+ * Removes all objects equivalent to the specified object
+ * within the range of specified indices.
+ */
+ public void removeObject (Object anObject, NSRange aRange)
+ {
+ if ( ( anObject == null ) || ( aRange == null ) ) return;
+
+ int loc = aRange.location();
+ int max = aRange.maxRange();
+ for ( int i = loc; i < max; i++ )
+ {
+ if ( anObject.equals( get( i ) ) )
+ {
+ remove( i );
+ i = i - 1;
+ max = max - 1;
+ }
+ }
+ }
+
+ /**
+ * Removes all instances of the specified object within the
+ * range of specified indices, comparing by reference.
+ */
+ public void removeIdenticalObject (Object anObject, NSRange aRange)
+ {
+ if ( ( anObject == null ) || ( aRange == null ) ) return;
+
+ int loc = aRange.location();
+ int max = aRange.maxRange();
+ for ( int i = loc; i < max; i++ )
+ {
+ if ( anObject == get( i ) )
+ {
+ remove( i );
+ i = i - 1;
+ max = max - 1;
+ }
+ }
+ }
+
+ /**
+ * Removes all objects in the specified collection from the array.
+ */
+ public void removeObjectsInArray (Collection aCollection)
+ {
+ removeAll( aCollection );
+ }
+
+ /**
+ * Removes all objects in the indices within the specified range
+ * from the array.
+ */
+ public void removeObjectsInRange (NSRange aRange)
+ {
+ if ( aRange == null ) return;
+
+ for ( int i = 0; i < aRange.length(); i++ )
+ {
+ remove( aRange.location() );
+ }
+ }
+
+ /**
+ * Replaces objects in the current range with objects from
+ * the specified range of the specified array. If currentRange
+ * is larger than otherRange, the extra objects are removed.
+ * If otherRange is larger than currentRange, the extra objects
+ * are added.
+ */
+ public void replaceObjectsInRange (NSRange currentRange,
+ List otherArray, NSRange otherRange)
+ {
+ if ( ( currentRange == null ) || ( otherArray == null ) ||
+ ( otherRange == null ) ) return;
+
+ // transform otherRange if out of bounds for array
+ if ( otherRange.maxRange() > otherArray.size() )
+ {
+ // TODO: Test this logic.
+ int loc = Math.min( otherRange.location(), otherArray.size() - 1 );
+ otherRange = new NSRange( loc, otherArray.size() - loc );
+ }
+
+ Object o;
+ List subList = subList(
+ currentRange.location(), currentRange.maxRange() );
+ int otherIndex = otherRange.location();
+ // TODO: Test this logic.
+ for ( int i = 0; i < subList.size(); i++ )
+ {
+ if ( otherIndex < otherRange.maxRange() )
+ { // set object
+ subList.set( i, otherArray.get( otherIndex ) );
+ }
+ else
+ { // remove extra elements from currentRange
+ subList.remove( i );
+ i--;
+ }
+ otherIndex++;
+ }
+ // TODO: Test this logic.
+ for ( int i = otherIndex; i < otherRange.maxRange(); i++ )
+ {
+ add( otherArray.get( i ) );
+ }
+ }
+
+ /**
+ * Clears the current array and then populates it with the
+ * contents of the specified collection.
+ */
+ public void setArray (Collection aCollection)
+ {
+ clear();
+ addAll( aCollection );
+ }
+
+ /**
+ * Removes all objects equivalent to the specified object.
+ */
+ public void removeObject (Object anObject)
+ {
+ remove( anObject );
+ }
+
+ /**
+ * Removes all occurences of the specified object,
+ * comparing by reference.
+ */
+ public void removeIdenticalObject (Object anObject)
+ {
+ EOObserverCenter.removeObserver( observer, anObject );
+ super.removeIdenticalObject( anObject );
+ }
+
+ /**
+ * Inserts the specified object into this array at the
+ * specified index.
+ */
+ public void insertObjectAtIndex (Object anObject, int anIndex)
+ {
+ add( anIndex, anObject );
+ }
+
+ /**
+ * Replaces the object at the specified index with the
+ * specified object.
+ */
+ public void replaceObjectAtIndex (int anIndex, Object anObject)
+ {
+ set( anIndex, anObject );
+ }
+
+ /**
+ * Adds the specified object to the end of this array.
+ */
+ public void addObject (Object anObject)
+ {
+ add( anObject );
+ }
+
+ // interface List: mutators
+
+ public void add(int index, Object element)
+ {
+ EOObserverCenter.addObserver( observer, element );
+ super.add( index, element );
+ }
+
+ public boolean add(Object o)
+ {
+ EOObserverCenter.addObserver( observer, o );
+ return super.add(o);
+ }
+
+ public boolean addAll(Collection coll)
+ {
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.addObserver( observer, it.next() );
+ }
+ return super.addAll(coll);
+ }
+
+ public boolean addAll(int index, Collection c)
+ {
+ Iterator it = c.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.addObserver( observer, it.next() );
+ }
+ return super.addAll( index, c );
+ }
+
+ public void clear()
+ {
+ Iterator it = iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.removeObserver( observer, it.next() );
+ }
+ super.clear();
+ }
+
+ public Object remove(int index)
+ {
+ EOObserverCenter.removeObserver( observer, get(index) );
+ return super.remove( index );
+ }
+
+ public boolean remove(Object o)
+ {
+ EOObserverCenter.removeObserver( observer, o );
+ return super.remove(o);
+ }
+
+ public boolean removeAll(Collection coll)
+ {
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.removeObserver( observer, it.next() );
+ }
+ return super.removeAll(coll);
+ }
+
+ public boolean retainAll(Collection coll)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object set(int index, Object element)
+ {
+ EOObserverCenter.removeObserver( observer, get(index) );
+ EOObserverCenter.addObserver( observer, element );
+ return super.set( index, element );
+ }
+}
+
+/*
+ * $Log$
+ * 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.1 2002/10/24 21:15:35 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.1 2001/02/20 16:38:55 mpowers
+ * MasterDetailAssociations now observe their controlled display group's
+ * objects for changes to that the parent object will be marked as updated.
+ * Before, only inserts and deletes to an object's items are registered.
+ * Also, moved ObservableArray to package access.
+ *
+ * Revision 1.1 2001/01/24 14:37:24 mpowers
+ * Contributing a delegate useful for debugging.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java
new file mode 100644
index 0000000..8b88615
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java
@@ -0,0 +1,57 @@
+/*
+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;
+
+/**
+* A simple extension of EODataSource that
+* allows for indexed insertion. The wotonomy
+* implementation of EODisplayGroup supports
+* this and will use it if possible. This is
+* useful for classes like the PropertyDataSource,
+* where the ordering of items in an indexed
+* property may be important.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public abstract class OrderedDataSource extends EODataSource
+{
+ /**
+ * Inserts the specified object into this data source,
+ * at the specified index.
+ */
+ public abstract void insertObjectAtIndex (
+ Object anObject, int anIndex );
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:19:57 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.2 2003/08/06 23:07:52 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.1 2001/01/24 14:10:53 mpowers
+ * Contributing OrderedDataSource, and PropertyDataSource extends it.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java
new file mode 100644
index 0000000..76f7219
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java
@@ -0,0 +1,549 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 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.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.internal.Introspector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* A data source that reads and writes to an indexed
+* property of a java object. This class is used by
+* MasterDetailAssociation to retreive objects from
+* the master display group.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 894 $
+*/
+public class PropertyDataSource extends OrderedDataSource
+{
+ protected Object source;
+ protected String key;
+ protected Class lastKnownType; // for best-guessing
+ protected EOClassDescription classDesc;
+ protected EOEditingContext context;
+
+ /**
+ * Creates a new PropertyDataSource with no editing context
+ * and will try to guess the appropriate class description
+ * when trying to create objects.
+ */
+ public PropertyDataSource()
+ {
+ this( null, (EOClassDescription) null );
+ }
+
+ /**
+ * Creates a new PropertyDataSource that uses the specified
+ * editing context, but will try to guess the appropriate
+ * class description when trying to create objects.
+ */
+ public PropertyDataSource( EOEditingContext aContext )
+ {
+ this( aContext, (EOClassDescription) null );
+ }
+
+ /**
+ * Creates a new PropertyDataSource that uses the specified
+ * editing context and vends objects of the specified class.
+ */
+ public PropertyDataSource(
+ EOEditingContext aContext, Class aClass )
+ {
+ this( aContext, EOClassDescription.classDescriptionForClass( aClass ) );
+ }
+
+ /**
+ * Creates a new PropertyDataSource that uses the specified
+ * editing context and vends objects of the specified
+ * class description.
+ */
+ public PropertyDataSource(
+ EOEditingContext aContext, EOClassDescription aClassDesc )
+ {
+ source = null;
+ key = null;
+ lastKnownType = null;
+ classDesc = aClassDesc;
+ context = aContext;
+ }
+
+ /**
+ * Provides the master object for detail display groups.
+ */
+ public Object source()
+ {
+ return source;
+ }
+
+ /**
+ * Allows a detail display group to set the master object.
+ */
+ public void setSource( Object anObject )
+ {
+ source = anObject;
+ }
+
+ /**
+ * Provides the detail key for detail display groups.
+ */
+ public String key()
+ {
+ return key;
+ }
+
+ /**
+ * Allows a detail display group to set the detail key.
+ */
+ public void setKey( String aKey )
+ {
+ key = aKey;
+ }
+
+ /**
+ * Inserts the specified object into this data source.
+ * Calls insertObjectAtIndex and appends to the end
+ * of the list.
+ */
+ public void insertObject ( Object anObject )
+ {
+ insertObjectAtIndex( anObject, -1 ); // trick to force to end
+ }
+
+ /**
+ * Inserts the specified object into this data source,
+ * at the specified index.
+ */
+ public void insertObjectAtIndex (
+ Object anObject, int anIndex )
+ {
+ if ( source == null ) return;
+ List list = readAsList();
+ if ( anIndex == -1 ) anIndex = list.size(); // force to end
+ if ( anIndex > list.size() ) anIndex = list.size(); // force to end
+ list.add( anIndex, anObject );
+ writeAsList( list );
+ }
+
+ /**
+ * Deletes the specified object from this data source.
+ */
+ public void deleteObject ( Object anObject )
+ {
+ if ( source == null ) return;
+ List list = readAsList();
+ list.remove( anObject );
+ writeAsList( list );
+ }
+
+ public EOEditingContext editingContext ()
+ {
+ return context;
+ }
+
+ /**
+ * Returns a List containing the objects in this
+ * data source.
+ */
+ public NSArray fetchObjects ()
+ {
+ if ( source == null ) return NSArray.EmptyArray;
+ return readAsList();
+ }
+
+ /**
+ * Returns a new instance of this class.
+ */
+ public EODataSource
+ dataSourceQualifiedByKey ( String aKey )
+ {
+ // determine the target class desc if possible
+ EOClassDescription keyClassDesc = null;
+ if ( classDesc != null )
+ {
+ keyClassDesc = classDesc.classDescriptionForDestinationKey( aKey );
+ }
+ return new PropertyDataSource( editingContext(), keyClassDesc );
+ }
+
+ /**
+ * Restricts this data source to vend those
+ * objects that are associated with the specified
+ * key on the specified object.
+ */
+ public void
+ qualifyWithRelationshipKey (
+ String aKey, Object anObject )
+ {
+ source = anObject;
+ key = aKey;
+ }
+
+ /**
+ * Returns the class description passed to the
+ * constructor, if any. If no class description and
+ * if the bound property is an indexed property,
+ * the type of the array is returned, otherwise
+ * this method returns null. This method is called
+ * by createObject().
+ */
+ public EOClassDescription
+ classDescriptionForObjects ()
+ {
+ // just return the class description if we have one
+ if ( classDesc != null ) return classDesc;
+
+ // otherwise, try to do some guesswork
+ EOClassDescription result = null;
+
+ // lastKnownType is not updated here
+ Class type = lastKnownType;
+
+ // if no last known type
+ if ( type == null )
+ {
+ // if source and key were specified
+ if ( ( source != null ) && ( key != null ) )
+ {
+ // try to get an array type
+ Method m = Introspector.getPropertyReadMethod(
+ source.getClass(), key, new Class[0] );
+ if ( m != null )
+ {
+ Class returnType = m.getReturnType();
+ if ( returnType.isArray() )
+ {
+ type = returnType.getComponentType();
+ }
+ }
+ else
+ {
+ throw new WotonomyException( "Key does not exist for object: " + key + " : " + source );
+ }
+ }
+
+ // does not update lastKnownType because
+ // we prefer to get that info from a fetch.
+ }
+
+ // if type has been determined
+ if ( type != null )
+ {
+ result =
+ EOClassDescription.classDescriptionForClass( type );
+ }
+
+ return result;
+ }
+
+ /**
+ * Calls getValue() and returns the result as a List.
+ * Sets lastKnownType to the retrieved type.
+ */
+ protected NSMutableArray readAsList()
+ {
+ Object value = getValue();
+ if ( value == null )
+ {
+ return new NSMutableArray();
+ }
+
+ Object o;
+ NSMutableArray result = new NSMutableArray();
+ boolean hasReadType = false;
+ lastKnownType = null;
+
+ // if instance of array, convert to list
+ if ( value.getClass().isArray() )
+ {
+ int count = Array.getLength( value );
+ for ( int i = 0; i < count; i++ )
+ {
+ o = Array.get( value, i );
+ if ( o != null )
+ {
+ // we've already found a type
+ if ( hasReadType )
+ {
+ // check that this matches the last known type
+ if ( o.getClass() != lastKnownType )
+ {
+ // not all of the same type: set to null
+ lastKnownType = null;
+ }
+ }
+ else // this is the first type we've found
+ {
+ // remember it
+ hasReadType = true;
+ lastKnownType = o.getClass();
+ }
+ }
+ result.add( o );
+ }
+ }
+ else
+ if ( value instanceof Collection )
+ {
+ // convert to list so we handle sets, etc.
+ Iterator i = ((Collection)value).iterator();
+ while ( i.hasNext() )
+ {
+ o = i.next();
+ if ( o != null )
+ {
+ // we've already found a type
+ if ( hasReadType )
+ {
+ // check that this matches the last known type
+ if ( o.getClass() != lastKnownType )
+ {
+ // not all of the same type: set to null
+ lastKnownType = null;
+ }
+ }
+ else // this is the first type we've found
+ {
+ // remember it
+ hasReadType = true;
+ lastKnownType = o.getClass();
+ }
+ }
+ result.add( o );
+ }
+ }
+ else
+ {
+ lastKnownType = null;
+ throw new WotonomyException( "PropertyDataSource: " +
+ "bound property was not an indexed property: " + key );
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts the specified List to lastKnownType
+ * and calls setValue().
+ */
+ protected void writeAsList( List anObjectList )
+ {
+ if ( source == null )
+ {
+ throw new WotonomyException( "PropertyDataSource: " +
+ "no source object: " + key );
+ }
+
+ Class c = source.getClass();
+ Method m = Introspector.getPropertyReadMethod( c, key, new Class[0] );
+ if ( m == null )
+ {
+ throw new WotonomyException( "Could not read property for object: "
+ + key + " : " + source + " (" + c + ")" );
+ }
+
+ Class returnType = m.getReturnType();
+
+ int count = anObjectList.size();
+ Object result = null;
+
+ if ( returnType.isArray() )
+ {
+ Class type = returnType.getComponentType();
+ result = Array.newInstance( type, count );
+ for ( int i = 0; i < count; i++ )
+ {
+ Array.set( result, i, anObjectList.get( i ) );
+ }
+ }
+ else
+ {
+ Collection collection = null;
+
+ if ( ! returnType.isInterface() )
+ {
+ try
+ {
+ collection = (Collection) returnType.newInstance();
+ }
+ catch ( Exception exc )
+ {
+ // no default constructor, leave null
+ }
+ }
+
+ // try to find an acceptable collections type
+ if ( collection == null )
+ {
+ if ( returnType.isAssignableFrom( NSMutableArray.class ) )
+ {
+ collection = new NSMutableArray();
+ }
+ else
+ if ( returnType.isAssignableFrom( LinkedList.class ) )
+ {
+ collection = new LinkedList();
+ }
+ else
+ if ( returnType.isAssignableFrom( ArrayList.class ) )
+ {
+ collection = new ArrayList();
+ }
+ else
+ if ( returnType.isAssignableFrom( HashSet.class ) )
+ {
+ collection = new HashSet();
+ }
+ else
+ if ( returnType.isAssignableFrom( TreeSet.class ) )
+ {
+ collection = new TreeSet();
+ }
+ }
+
+ if ( collection == null )
+ {
+ throw new WotonomyException( "Could not create a collection of type: " + returnType );
+ }
+
+ collection.addAll( anObjectList );
+ result = collection;
+ }
+
+ setValue( result );
+ }
+
+ /**
+ * Returns the value of the indexed property
+ * specified by qualifyWithRelationshipKey.
+ */
+ protected Object getValue()
+ {
+ if ( source instanceof EOKeyValueCoding )
+ {
+ return ((EOKeyValueCoding)source).valueForKey( key );
+ }
+ return EOKeyValueCodingSupport.valueForKey( source, key );
+ }
+
+ /**
+ * Sets the value of the indexed property
+ * specified by qualifyWithRelationshipKey.
+ * The argument is assumed to be of appropriate
+ * type for the property. EOObserverCenter is
+ * notified that the object will change.
+ */
+ protected void setValue( Object aValue )
+ {
+ EOClassDescription sourceDesc =
+ EOClassDescription.classDescriptionForClass( source.getClass() );
+
+
+ // if we're not editing a relationship (?)
+// if ( ! sourceDesc.toManyRelationshipKeys().containsObject( key ) )
+ {
+ // mark the parent as changed
+ EOObserverCenter.notifyObserversObjectWillChange( source );
+ }
+
+
+ if ( source instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)source).takeValueForKey( aValue, key );
+ }
+ else
+ {
+ EOKeyValueCodingSupport.takeValueForKey( source, aValue, key );
+ }
+ }
+
+}
+
+/*
+ * $Log$
+ * 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.14 2003/01/18 23:30:42 mpowers
+ * WODisplayGroup now compiles.
+ *
+ * Revision 1.13 2002/10/24 21:15:36 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.12 2002/10/24 18:18:12 mpowers
+ * NSArray's are now considered read-only, so we can return our internal
+ * representation to reduce unnecessary object allocation.
+ *
+ * Revision 1.11 2002/04/15 21:55:33 mpowers
+ * Catching a condition where the get may not return the value passed to set.
+ *
+ * Revision 1.10 2002/03/08 23:20:37 mpowers
+ * insertObject now calls insertObjectAtIndex.
+ *
+ * Revision 1.9 2001/06/05 19:10:41 mpowers
+ * Better handling of null properties.
+ *
+ * Revision 1.8 2001/05/21 14:03:35 mpowers
+ * Added a convenience constructor for java classes.
+ *
+ * Revision 1.7 2001/04/30 13:15:24 mpowers
+ * Child contexts re-initializing objects invalidated in parent now
+ * propery transpose relationships.
+ *
+ * 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/27 23:37:20 mpowers
+ * Now using EOClassDescription in the EODataSource class, as we should.
+ *
+ * Revision 1.3 2001/03/29 03:29:49 mpowers
+ * Now using KeyValueCoding and Support instead of Introspector.
+ *
+ * Revision 1.2 2001/01/24 14:10:53 mpowers
+ * Contributing OrderedDataSource, and PropertyDataSource extends it.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:46:50 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.3 2000/12/20 16:25:35 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java
new file mode 100644
index 0000000..e12fda0
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java
@@ -0,0 +1,259 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 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.internal;
+
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.internal.Introspector;
+
+/**
+* A Surrogate is a special object that can be used in a display
+* group when you wish to emulate other objects or modify their
+* behaviors. Because it is a Map, it makes use of Introspector's
+* ability to treat keys in a map as if they were properties to
+* implement the following features.
+* <ul>
+* <li>By default, Surrogate works like a Map, and reading and
+* writing properties to a Surrogate gets and puts values in the
+* Map.</li>
+* <li>If one or more delegate objects are specified, property keys
+* that do not exist in the map are read from and written to the
+* delegate object.</li>
+* <li>If a default value is specified, that value will be returned
+* for all property reads that do not exist in the map or in the
+* delegate object. (Subsequent writes to those properties will
+* create a key in the map and then subsequent reads will read not
+* read the default object.)</li>
+* <li>Subclasses can override the get(Object) method to further
+* customize the behavior of a Surrogate.
+* </ul>
+*
+* @author michael@mpowers.net
+* @date $Date: 2006-02-18 17:46:44 -0500 (Sat, 18 Feb 2006) $
+* @revision $Revision: 900 $
+*/
+public class Surrogate extends NSMutableDictionary
+{
+ protected Object[] delegates;
+ protected Object defaultValue;
+
+ /**
+ * Default constructor with no delegate object and no default value.
+ */
+ public Surrogate()
+ {
+ delegates = null;
+ defaultValue = null;
+ }
+
+ /**
+ * Constructor specifying a delegate object.
+ */
+ public Surrogate( Object[] aDelegateArray )
+ {
+ setDelegates( aDelegateArray );
+ }
+
+ /**
+ * Constructor specifying a default value.
+ */
+ public Surrogate( Object aDefault )
+ {
+ setDefaultValue( aDefault );
+ }
+
+ /**
+ * Constructor specifying a delegate object and a default value.
+ */
+ public Surrogate( Object[] aDelegateArray, Object aDefault )
+ {
+ setDelegates( aDelegateArray );
+ setDefaultValue( aDefault );
+ }
+
+ /**
+ * Returns the first delegate object, or null if no delegates exist.
+ */
+ public Object getDelegate()
+ {
+ if ( delegates == null ) return null;
+ if ( delegates.length == 0 ) return null;
+ return delegates[0];
+ }
+
+ /**
+ * Sets the delegate object list to contain only the
+ * specified object.
+ */
+ public void setDelegate( Object aDelegate )
+ {
+ setDelegates( new Object[] { aDelegate } );
+ }
+
+ /**
+ * Returns the list of delegates in the order in which
+ * they are consulted.
+ */
+ public Object[] getDelegates()
+ {
+ if ( delegates == null ) delegates = new Object[0];
+ return delegates;
+ }
+
+ /**
+ * Sets the list of delegates in the order in which they
+ * will be consulted.
+ */
+ public void setDelegates( Object[] aDelegateArray )
+ {
+ delegates = aDelegateArray;
+ }
+
+ /**
+ * Returns the current default value, or null if no default exists.
+ */
+ public Object getDefaultValue()
+ {
+ return defaultValue;
+ }
+
+ /**
+ * Sets the default value.
+ */
+ public void setDefaultValue( Object aDefault )
+ {
+ defaultValue = aDefault;
+ }
+
+ /**
+ * Called by get to retrieve a value from the internal map.
+ * This implementation simply calls super.get().
+ */
+ public Object directGet( Object aKey )
+ {
+ return super.get( aKey );
+ }
+
+ /**
+ * Called by put to retrieve a value from the internal map.
+ * This implementation simply calls super.put().
+ */
+ public Object directPut( Object aKey, Object aValue )
+ {
+ return super.put( aKey, aValue );
+ }
+
+ /**
+ * Overridden to consult each delegate before
+ * checking the internal list of keys. No matching
+ * key is found, returns the default object, or
+ * null if no default object exists.
+ */
+ public Object get( Object aKey )
+ {
+ // check all delegates in order
+ int i, j;
+ Object[] list = getDelegates();
+ String[] properties;
+ for ( i = 0; i < list.length; i++ )
+ {
+ // for each delegate
+ properties =
+ Introspector.getReadPropertiesForObject( list[i] );
+ for ( j = 0; j < properties.length; j++ )
+ {
+ // if delegate has property
+ if ( properties[j].equals( aKey ) )
+ {
+ // use this delegate
+ return Introspector.get( list[i], aKey.toString() );
+ }
+ }
+ }
+
+ // return from internal map
+ Object result = directGet( aKey );
+ if ( result == null )
+ {
+ // if not in map, return default object
+ result = getDefaultValue();
+ }
+ return result;
+ }
+
+ /**
+ * Overridden to attempt to write each delegate, writing to
+ * only the first successful delegate, before storing the
+ * value in the internal map.
+ */
+ public Object put( Object aKey, Object aValue )
+ {
+ // check all delegates in order
+ int i, j;
+ Object[] list = getDelegates();
+ String[] properties;
+ for ( i = 0; i < list.length; i++ )
+ {
+ // for each delegate
+ properties =
+ Introspector.getWritePropertiesForObject( list[i] );
+ for ( j = 0; j < properties.length; j++ )
+ {
+ // if delegate has property
+ if ( properties[j].equals( aKey ) )
+ {
+ // use this delegate
+ EOObserverCenter.notifyObserversObjectWillChange( list[i] );
+ return Introspector.set( list[i], aKey.toString(), aValue );
+ }
+ }
+ }
+
+ // set on internal map
+ EOObserverCenter.notifyObserversObjectWillChange( this );
+ return directPut( aKey, aValue );
+ }
+
+ /**
+ * Overridden to compare by reference.
+ */
+ public boolean equals( Object anObject )
+ {
+ return ( this == anObject );
+ }
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/18 22:46:44 cgruber
+ * Add Surrogate map from .util into control's internal package, and fix imports.
+ *
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:52:21 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:48 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html
new file mode 100644
index 0000000..c32d3c7
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html
@@ -0,0 +1,6 @@
+<body>
+<p>
+Support classes needed by the ui and web packages.
+This package roughly corresponds to the eocontrol package.
+</p>
+</body>