Browse Source

Added a simple deep cloning that can replace all of our odd post-fixup
piece-meal stuff with proper full-object-graph shared reference cloning.

Paul Speed 9 years ago
parent
commit
70154f1b1d

+ 81 - 0
jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ *  Provides custom cloning for a particular object type.  Once
+ *  registered with the Cloner, this function object will be called twice
+ *  for any cloned object that matches the class for which it was registered.
+ *  It will first call cloneObject() to shallow clone the object and then call
+ *  cloneFields()  to deep clone the object's values.
+ *
+ *  <p>This two step process is important because this is what allows
+ *  circular references in the cloned object graph.</p>
+ *
+ *  @author    Paul Speed
+ */
+public interface CloneFunction<T> {
+
+    /**
+     *  Performs a shallow clone of the specified object.  This is similar
+     *  to the JmeCloneable.clone() method in semantics and is the first part
+     *  of a two part cloning process.  Once the shallow clone is created, it
+     *  is cached and CloneFunction.cloneFields() is called.  In this way, 
+     *  the CloneFunction interface can completely take over the JmeCloneable
+     *  style cloning for an object that doesn't otherwise implement that interface.
+     *
+     *  @param cloner The cloner performing the cloning operation.
+     *  @param original The original object that needs to be cloned.
+     */
+    public T cloneObject( Cloner cloner, T original );
+ 
+ 
+    /**
+     *  Performs a deep clone of the specified clone's fields.  This is similar
+     *  to the JmeCloneable.cloneFields() method in semantics and is the second part
+     *  of a two part cloning process.  Once the shallow clone is created, it
+     *  is cached and CloneFunction.cloneFields() is called.  In this way, 
+     *  the CloneFunction interface can completely take over the JmeCloneable
+     *  style cloning for an object that doesn't otherwise implement that interface.
+     * 
+     *  @param cloner The cloner performing the cloning operation.
+     *  @param clone The clone previously returned from cloneObject().
+     *  @param original The original object that was cloned.  This is provided for
+     *              the very special case where field cloning needs to refer to
+     *              the original object.  Mostly the necessary fields should already
+     *              be on the clone.
+     */
+    public void cloneFields( Cloner cloner, T clone, T original );
+     
+}

+ 344 - 0
jme3-core/src/main/java/com/jme3/util/clone/Cloner.java

@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *  A deep clone utility that provides similar object-graph-preserving
+ *  qualities to typical serialization schemes.  An internal registry
+ *  of cloned objects is kept to be used by other objects in the deep
+ *  clone process that implement JmeCloneable.
+ *
+ *  <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 
+ *  will be thrown.</p>
+ *
+ *  <p>Enhanced object cloning is done in a two step process.  First,
+ *  the object is cloned using the normal Java clone() method and stored
+ *  in the clone registry.  After that, if it implements JmeCloneable then
+ *  its cloneFields() method is called to deep clone any of the fields.
+ *  This two step process has a few benefits.  First, it means that objects
+ *  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> 
+ *
+ *  <p>Similar to Java serialization, the handling of specific object
+ *  types can be customized.  This allows certain objects to be cloned gracefully
+ *  even if they aren't normally Cloneable.  This can also be used as a
+ *  sort of filter to keep certain types of objects from being cloned.
+ *  (For example, adding the IdentityCloneFunction for Mesh.class would cause
+ *  all mesh instances to be shared with the original object graph.)</p>
+ *
+ *  <p>By default, the Cloner registers serveral default clone functions
+ *  as follows:</p>
+ *  <ul>
+ *  <li>java.util.ArrayList: ListCloneFunction
+ *  <li>java.util.LinkedList: ListCloneFunction
+ *  <li>java.util.concurrent.CopyOnWriteArrayList: ListCloneFunction
+ *  <li>java.util.Vector: ListCloneFunction
+ *  <li>java.util.Stack: ListCloneFunction
+ *  <li>com.jme3.util.SafeArrayList: ListCloneFunction
+ *  </ul>
+ *
+ *  <p>Usage:</p>
+ *  <pre>
+ *  // Example 1: using an instantiated, reusable cloner.
+ *  Cloner cloner = new Cloner();
+ *  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 {
+ 
+    /**
+     *  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.
+     */
+    public Cloner() {
+        // Register some standard types
+        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.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 
+     *      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. 
+     *  </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.
+     */
+    @SuppressWarnings("unchecked")
+    private <T> Class<T> objectClass( T object ) {
+        // This should be 100% allowed without a cast but Java generics
+        // is not that smart sometimes.
+        // 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 
+     *      called to clone the object.
+     *  <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. 
+     *  </ul>
+     *
+     *  <p>The abililty to selectively use clone functions is useful when
+     *  being called from a clone function.</p>
+     *
+     *  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); 
+        
+        // Check the index to see if we already have it
+        Object clone = index.get(object);
+        if( clone != null ) {
+            return type.cast(clone); 
+        }
+        
+        // See if there is a custom function... that trumps everything.
+        CloneFunction<T> f = getCloneFunction(type); //(CloneFunction<T>)functions.get(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); 
+            
+            // Now call the function again to deep clone the fields
+            f.cloneFields(this, result, object);
+            
+            return result;           
+        }
+ 
+        if( object.getClass().isArray() ) {
+            // Perform an array clone        
+            clone = arrayClone(object);
+            
+            // Array clone already indexes the clone
+        } 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); 
+        } else {
+            throw new IllegalArgumentException("Object is not cloneable, type:" + type);
+        }
+        
+        // Finally, check to see if the object implements special field cloning
+        // behavior.       
+        if( clone instanceof JmeCloneable ) {
+            ((JmeCloneable)clone).cloneFields(this, object);
+        }
+        
+        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 
+     *  ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList.
+     */
+    public <T> void setCloneFunction( Class<T> type, CloneFunction<T> function ) {
+        if( function == null ) {
+            functions.remove(type);
+        } else {
+            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); 
+    } 
+ 
+    /**
+     *  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
+     *  it is called.  That's because these are shallow clones and have not (and may
+     *  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>  
+     */   
+    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 ) {            
+                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);       
+            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 elementType = type.getComponentType();
+        int size = Array.getLength(object); 
+        Object clone = Array.newInstance(elementType, size);
+ 
+        // Store the clone for later lookups
+        index.put(object, clone); 
+        
+        if( elementType.isPrimitive() ) {
+            // Then our job is a bit easier
+            System.arraycopy(object, 0, clone, 0, size);
+        } else {
+            // Else it's an object array so we'll clone it and its children
+            for( int i = 0; i < size; i++ ) {
+                Object element = clone(Array.get(object, i));
+                Array.set(clone, i, element);
+            }
+        }           
+        
+        return type.cast(clone);
+    }
+}

+ 58 - 0
jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ *  A CloneFunction implementation that simply returns the 
+ *  the passed object without cloning it.  This is useful for
+ *  forcing some object types (like Meshes) to be shared between
+ *  the original and cloned object graph.
+ *
+ *  @author    Paul Speed
+ */
+public class IdentityCloneFunction<T> implements CloneFunction<T> {
+
+    /**
+     *  Returns the object directly.
+     */
+    public T cloneObject( Cloner cloner, T object ) {
+        return object;
+    }
+ 
+    /**
+     *  Does nothing.
+     */    
+    public void cloneFields( Cloner cloner, T clone, T object ) {
+    }
+}

+ 87 - 0
jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ *  Indicates an object that wishes to more actively participate in the
+ *  two-part deep copying process provided by the Cloner.  Objects implementing
+ *  this interface can access the already cloned object graph to resolve
+ *  their local dependencies in a way that will be equivalent to the
+ *  original object graph.  In other words, if two objects in the graph
+ *  share the same target reference then the cloned version will share
+ *  the cloned reference. 
+ *  
+ *  <p>For example, if an object wishes to deep clone one of its fields
+ *  then it will call cloner.clone(object) instead of object.clone().
+ *  The cloner will keep track of any clones already created for 'object'
+ *  and return that instead of a new clone.</p>
+ *
+ *  <p>Cloning of a JmeCloneable object is done in two parts.  First,
+ *  the standard Java clone() method is called to create a shallow clone
+ *  of the object.  Second, the cloner wil lcall the cloneFields() method
+ *  to let the object deep clone any of its fields that should be cloned.</p>
+ *
+ *  <p>This two part process is necessary to facilitate circular references.
+ *  When an object calls cloner.clone() during its cloneFields() method, it
+ *  may get only a shallow copy that will be filled in later.</p>
+ *
+ *  @author    Paul Speed
+ */
+public interface JmeCloneable extends Cloneable {
+
+    /**
+     *  Performs a shallow clone of the object.
+     */
+    public Object clone();     
+
+    /**
+     *  Implemented to perform deep cloning for this object, resolving
+     *  local cloned references using the specified cloner.  The object
+     *  can call cloner.clone(fieldValue) to deep clone any of its fields.
+     * 
+     *  <p>Note: during normal clone operations the original object
+     *  will not be needed as the clone has already had all of the fields
+     *  shallow copied.</p>
+     *
+     *  @param cloner The cloner that is performing the cloning operation.  The 
+     *              cloneFields method can call back into the cloner to make
+     *              clones if its subordinate fields.     
+     *  @param original The original object from which this object was cloned.
+     *              This is provided for the very rare case that this object needs
+     *              to refer to its original for some reason.  In general, all of
+     *              the relevant values should have been transferred during the
+     *              shallow clone and this object need merely clone what it wants.
+     */
+    public void cloneFields( Cloner cloner, Object original ); 
+}

+ 70 - 0
jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+import java.util.List;
+
+/**
+ *  A CloneFunction implementation that deep clones a list by
+ *  creating a new list and cloning its values using the cloner.
+ *
+ *  @author    Paul Speed
+ */
+public class ListCloneFunction<T extends List> implements CloneFunction<T> {
+
+    public T cloneObject( Cloner cloner, T object ) {         
+        try {
+            T clone = cloner.javaClone(object);         
+            return clone;
+        } catch( CloneNotSupportedException e ) {
+            throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e);
+        }
+    }
+     
+    /**
+     *  Clones the elements of the list.
+     */    
+    @SuppressWarnings("unchecked")
+    public void cloneFields( Cloner cloner, T clone, T object ) {
+        for( int i = 0; i < clone.size(); i++ ) {
+            // Need to clone the clones... because T might
+            // have done something special in its clone method that
+            // we will have to adhere to.  For example, clone may have nulled
+            // out some things or whatever that might be implementation specific.
+            // At any rate, if it's a proper clone then the clone will already
+            // have shallow versions of the elements that we can clone.
+            clone.set(i, cloner.clone(clone.get(i)));
+        }
+    }
+}
+