Pārlūkot izejas kodu

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'.

Paul Speed 9 gadi atpakaļ
vecāks
revīzija
2028f3b3f8
1 mainītis faili ar 119 papildinājumiem un 71 dzēšanām
  1. 119 71
      jme3-core/src/main/java/com/jme3/util/clone/Cloner.java

+ 119 - 71
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;
  *  <p>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.</p>
  *
  *  <p>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.</p> 
+ *  to resolve circular references.</p>
  *
  *  <p>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);
- *   
+ *
  *  </pre>
  *
  *  @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<Object, Object> index = new IdentityHashMap<Object, Object>();
- 
+
     /**
      *  Custom functions for cloning objects.
      */
     private Map<Class, CloneFunction> functions = new HashMap<Class, CloneFunction>();
- 
+
     /**
      *  Cache the clone methods once for all cloners.
-     */   
+     */
     private static final Map<Class, Method> 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> T deepClone( T object ) {
         return new Cloner().clone(object);
-    }     
- 
+    }
+
     /**
      *  Deeps clones the specified object, reusing previous clones when possible.
-     * 
+     *
      *  <p>Object cloning priority works as follows:</p>
      *  <ul>
      *  <li>If the object has already been cloned then its clone is returned.
      *  <li>If there is a custom CloneFunction then it is called to clone the object.
-     *  <li>If the object implements Cloneable then its clone() method is called, arrays are 
+     *  <li>If the object implements Cloneable then its clone() method is called, arrays are
      *      deep cloned with entries passing through clone().
      *  <li>If the object implements JmeCloneable then its cloneFields() method is called on the
      *      clone.
-     *  <li>Else an IllegalArgumentException is thrown. 
+     *  <li>Else an IllegalArgumentException is thrown.
      *  </ul>
      *
      *  Note: objects returned by this method may not have yet had their cloneField()
      *  method called.
-     */   
+     */
     public <T> 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<T>)object.getClass();
     }
- 
+
     /**
      *  Deeps clones the specified object, reusing previous clones when possible.
-     * 
+     *
      *  <p>Object cloning priority works as follows:</p>
      *  <ul>
      *  <li>If the object has already been cloned then its clone is returned.
-     *  <li>If useFunctions is true and there is a custom CloneFunction then it is 
+     *  <li>If useFunctions is true and there is a custom CloneFunction then it is
      *      called to clone the object.
-     *  <li>If the object implements Cloneable then its clone() method is called, arrays are 
+     *  <li>If the object implements Cloneable then its clone() method is called, arrays are
      *      deep cloned with entries passing through clone().
      *  <li>If the object implements JmeCloneable then its cloneFields() method is called on the
      *      clone.
-     *  <li>Else an IllegalArgumentException is thrown. 
+     *  <li>Else an IllegalArgumentException is thrown.
      *  </ul>
      *
      *  <p>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> T clone( T object, boolean useFunctions ) {
+
         if( object == null ) {
             return null;
         }
-        Class<T> type = objectClass(object); 
-        
+
+        if( log.isLoggable(Level.FINER) ) {
+            log.finer("cloning:" + object.getClass() + "@" + System.identityHashCode(object));
+        }
+
+        Class<T> 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<T> f = getCloneFunction(type); 
+        CloneFunction<T> 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 <T> void setCloneFunction( Class<T> type, CloneFunction<T> 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 <T> CloneFunction<T> getCloneFunction( Class<T> type ) {
-        return (CloneFunction<T>)functions.get(type); 
-    } 
- 
+        return (CloneFunction<T>)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 <T> 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.
      *
      *  <p>This method is provided as a convenient way for CloneFunctions to call
-     *  clone() and objects without necessarily knowing their real type.</p>  
-     */   
+     *  clone() and objects without necessarily knowing their real type.</p>
+     */
     public <T> 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> 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<T> type = objectClass(object); 
+        Class<T> 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);
     }
 }