|
@@ -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);
|
|
|
+ }
|
|
|
+}
|