diff --git a/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java new file mode 100644 index 000000000..cb5873007 --- /dev/null +++ b/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. + * + *

This two step process is important because this is what allows + * circular references in the cloned object graph.

+ * + * @author Paul Speed + */ +public interface CloneFunction { + + /** + * 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 ); + +} 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 new file mode 100644 index 000000000..a927223ae --- /dev/null +++ b/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. + * + *

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.

+ * + *

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.

+ * + *

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

+ * + *

By default, the Cloner registers serveral default clone functions + * as follows:

+ * + * + *

Usage:

+ *
+ *  // 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);
+ *   
+ *  
+ * + * @author Paul Speed + */ +public class Cloner { + + /** + * 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. + */ + 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 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 + * 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. + *
+ * + * 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. + */ + @SuppressWarnings("unchecked") + private Class 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)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 + * called to clone the object. + *
  • 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. + *
+ * + *

The abililty to selectively use clone functions is useful when + * being called from a clone function.

+ * + * 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); + + // 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 f = getCloneFunction(type); //(CloneFunction)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 void setCloneFunction( Class type, CloneFunction 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 CloneFunction getCloneFunction( Class type ) { + return (CloneFunction)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. + * + *

This method is provided as a convenient way for CloneFunctions to call + * 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 ) { + 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); + 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 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); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java new file mode 100644 index 000000000..ac3dce6ee --- /dev/null +++ b/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 implements CloneFunction { + + /** + * Returns the object directly. + */ + public T cloneObject( Cloner cloner, T object ) { + return object; + } + + /** + * Does nothing. + */ + public void cloneFields( Cloner cloner, T clone, T object ) { + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java new file mode 100644 index 000000000..28049e0f1 --- /dev/null +++ b/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. + * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

Note: during normal clone operations the original object + * will not be needed as the clone has already had all of the fields + * shallow copied.

+ * + * @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 ); +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java new file mode 100644 index 000000000..a1f269d67 --- /dev/null +++ b/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 implements CloneFunction { + + 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))); + } + } +} +