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:
*
* If the object has already been cloned then its clone is returned.
* If 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.
*
*
* 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 extends T> type = objectClass(object);
+ Class extends T> 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);
}
}