From 2028f3b3f864884d03d4ee9bf2b86f87688ee691 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 27 Mar 2016 04:53:12 -0400 Subject: [PATCH] Added 'finer' logging for the clone() method to provide visibility for debugging. Added a setClonedValue() method to force uncloned or precloned references in some specific use-cases. Added an isCloned() method to tell if an object has already been cloned in this cloner's 'session'. --- .../main/java/com/jme3/util/clone/Cloner.java | 190 +++++++++++------- 1 file changed, 119 insertions(+), 71 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java index cc046b28a..be34755be 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java @@ -37,6 +37,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.logging.Logger; +import java.util.logging.Level; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -49,7 +51,7 @@ import java.util.concurrent.ConcurrentHashMap; *

By default, objects that do not implement JmeCloneable will * be treated like normal Java Cloneable objects. If the object does * not implement the JmeCloneable or the regular JDK Cloneable interfaces - * AND has no special handling defined then an IllegalArgumentException + * AND has no special handling defined then an IllegalArgumentException * will be thrown.

* *

Enhanced object cloning is done in a two step process. First, @@ -60,7 +62,7 @@ import java.util.concurrent.ConcurrentHashMap; * can easily have a regular shallow clone implementation just like any * normal Java objects. Second, the deep cloning of fields happens after * creation wich means that the clone is available to future field cloning - * to resolve circular references.

+ * to resolve circular references.

* *

Similar to Java serialization, the handling of specific object * types can be customized. This allows certain objects to be cloned gracefully @@ -87,31 +89,33 @@ import java.util.concurrent.ConcurrentHashMap; * Foo fooClone = cloner.clone(foo); * cloner.clearIndex(); // prepare it for reuse * Foo fooClone2 = cloner.clone(foo); - * + * * // Example 2: using the utility method that self-instantiates a temporary cloner. * Foo fooClone = Cloner.deepClone(foo); - * + * * * * @author Paul Speed */ public class Cloner { - + + static Logger log = Logger.getLogger(Cloner.class.getName()); + /** * Keeps track of the objects that have been cloned so far. - */ + */ private IdentityHashMap index = new IdentityHashMap(); - + /** * Custom functions for cloning objects. */ private Map functions = new HashMap(); - + /** * Cache the clone methods once for all cloners. - */ + */ private static final Map methodCache = new ConcurrentHashMap<>(); - + /** * Creates a new cloner with only default clone functions and an empty * object index. @@ -121,41 +125,41 @@ public class Cloner { ListCloneFunction listFunction = new ListCloneFunction(); functions.put(java.util.ArrayList.class, listFunction); functions.put(java.util.LinkedList.class, listFunction); - functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction); + functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction); functions.put(java.util.Vector.class, listFunction); functions.put(java.util.Stack.class, listFunction); functions.put(com.jme3.util.SafeArrayList.class, listFunction); } - + /** * Convenience utility function that creates a new Cloner, uses it to * deep clone the object, and then returns the result. */ public static T deepClone( T object ) { return new Cloner().clone(object); - } - + } + /** * Deeps clones the specified object, reusing previous clones when possible. - * + * *

Object cloning priority works as follows:

* * * Note: objects returned by this method may not have yet had their cloneField() * method called. - */ + */ public T clone( T object ) { return clone(object, true); } - + /** * Internal method to work around a Java generics typing issue by * isolating the 'bad' case into a method with suppressed warnings. @@ -167,20 +171,20 @@ public class Cloner { // Wrapping it in a method at least isolates the warning suppression return (Class)object.getClass(); } - + /** * Deeps clones the specified object, reusing previous clones when possible. - * + * *

Object cloning priority works as follows:

*
    *
  • If the object has already been cloned then its clone is returned. - *
  • If useFunctions is true and there is a custom CloneFunction then it is + *
  • If useFunctions is true and there is a custom CloneFunction then it is * called to clone the object. - *
  • If the object implements Cloneable then its clone() method is called, arrays are + *
  • If the object implements Cloneable then its clone() method is called, arrays are * deep cloned with entries passing through clone(). *
  • If the object implements JmeCloneable then its cloneFields() method is called on the * clone. - *
  • Else an IllegalArgumentException is thrown. + *
  • Else an IllegalArgumentException is thrown. *
* *

The abililty to selectively use clone functions is useful when @@ -188,71 +192,94 @@ public class Cloner { * * Note: objects returned by this method may not have yet had their cloneField() * method called. - */ + */ public T clone( T object, boolean useFunctions ) { + if( object == null ) { return null; } - Class type = objectClass(object); - + + if( log.isLoggable(Level.FINER) ) { + log.finer("cloning:" + object.getClass() + "@" + System.identityHashCode(object)); + } + + Class type = objectClass(object); + // Check the index to see if we already have it Object clone = index.get(object); if( clone != null ) { - return type.cast(clone); + if( log.isLoggable(Level.FINER) ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as cached:" + clone.getClass() + "@" + System.identityHashCode(clone)); + } + return type.cast(clone); } - + // See if there is a custom function... that trumps everything. - CloneFunction f = getCloneFunction(type); + CloneFunction f = getCloneFunction(type); if( f != null ) { T result = f.cloneObject(this, object); - + // Store the object in the identity map so that any circular references - // are resolvable. - index.put(object, result); - + // are resolvable. + index.put(object, result); + // Now call the function again to deep clone the fields f.cloneFields(this, result, object); - - return result; + + if( log.isLoggable(Level.FINER) ) { + if( result == null ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as transformed:null"); + } else { + log.finer("clone:" + object.getClass() + "@" + System.identityHashCode(object) + + " as transformed:" + result.getClass() + "@" + System.identityHashCode(result)); + } + } + return result; } - + if( object.getClass().isArray() ) { - // Perform an array clone + // Perform an array clone clone = arrayClone(object); - + // Array clone already indexes the clone } else if( object instanceof JmeCloneable ) { // Use the two-step cloning semantics clone = ((JmeCloneable)object).jmeClone(); - + // Store the object in the identity map so that any circular references // are resolvable - index.put(object, clone); - + index.put(object, clone); + ((JmeCloneable)clone).cloneFields(this, object); } else if( object instanceof Cloneable ) { - + // Perform a regular Java shallow clone try { clone = javaClone(object); } catch( CloneNotSupportedException e ) { throw new IllegalArgumentException("Object is not cloneable, type:" + type, e); } - + // Store the object in the identity map so that any circular references // are resolvable - index.put(object, clone); + index.put(object, clone); } else { throw new IllegalArgumentException("Object is not cloneable, type:" + type); } - + + if( log.isLoggable(Level.FINER) ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as " + clone.getClass() + "@" + System.identityHashCode(clone)); + } return type.cast(clone); } - + /** * Sets a custom CloneFunction for the exact Java type. Note: no inheritence * checks are performed so a function must be registered for each specific type - * that it handles. By default ListCloneFunction is registered for + * that it handles. By default ListCloneFunction is registered for * ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList. */ public void setCloneFunction( Class type, CloneFunction function ) { @@ -262,24 +289,45 @@ public class Cloner { functions.put(type, function); } } - + /** * Returns a previously registered clone function for the specified type or null * if there is no custom clone function for the type. - */ + */ @SuppressWarnings("unchecked") public CloneFunction getCloneFunction( Class type ) { - return (CloneFunction)functions.get(type); - } - + return (CloneFunction)functions.get(type); + } + + /** + * Forces an object to be added to the indexing cache such that attempts + * to clone the 'original' will always result in the 'clone' being returned. + * This can be used to stub out specific values from being cloned or to + * force global shared instances to be used even if the object is cloneable + * normally. + */ + public void setClonedValue( T original, T clone ) { + index.put(original, clone); + } + + /** + * Returns true if the specified object has already been cloned + * by this cloner during this session. Cloned objects are cached + * for later use and it's sometimes convenient to know if some + * objects have already been cloned. + */ + public boolean isCloned( Object o ) { + return index.containsKey(o); + } + /** * Clears the object index allowing the cloner to be reused for a brand new * cloning operation. */ public void clearIndex() { index.clear(); - } - + } + /** * Performs a raw shallow Java clone using reflection. This call does NOT * check against the clone index and so will return new objects every time @@ -287,51 +335,51 @@ public class Cloner { * not ever, depending on the caller) get resolved. * *

This method is provided as a convenient way for CloneFunctions to call - * clone() and objects without necessarily knowing their real type.

- */ + * clone() and objects without necessarily knowing their real type.

+ */ public T javaClone( T object ) throws CloneNotSupportedException { Method m = methodCache.get(object.getClass()); if( m == null ) { try { // Lookup the method and cache it m = object.getClass().getMethod("clone"); - } catch( NoSuchMethodException e ) { + } catch( NoSuchMethodException e ) { throw new CloneNotSupportedException("No public clone method found for:" + object.getClass()); } methodCache.put(object.getClass(), m); - + // Note: yes we might cache the method twice... but so what? } - + try { - Class type = objectClass(object); + Class type = objectClass(object); return type.cast(m.invoke(object)); } catch( IllegalAccessException | InvocationTargetException e ) { throw new RuntimeException("Error cloning object of type:" + object.getClass(), e); - } + } } - + /** * Clones a primitive array by coping it and clones an object * array by coping it and then running each of its values through * Cloner.clone(). */ protected T arrayClone( T object ) { - + // Java doesn't support the cloning of arrays through reflection unless // you open access to Object's protected clone array... which requires // elevated privileges. So we will do a work-around that is slightly less // elegant. // This should be 100% allowed without a case but Java generics // is not that smart - Class type = objectClass(object); + Class type = objectClass(object); Class elementType = type.getComponentType(); - int size = Array.getLength(object); + int size = Array.getLength(object); Object clone = Array.newInstance(elementType, size); - + // Store the clone for later lookups - index.put(object, clone); - + index.put(object, clone); + if( elementType.isPrimitive() ) { // Then our job is a bit easier System.arraycopy(object, 0, clone, 0, size); @@ -341,8 +389,8 @@ public class Cloner { Object element = clone(Array.get(object, i)); Array.set(clone, i, element); } - } - + } + return type.cast(clone); } }