summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java
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/KeyValueCodingUtilities.java
Initial import from SVN
Diffstat (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java')
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java740
1 files changed, 740 insertions, 0 deletions
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.
+ *
+ *
+ */
+
+