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.
This commit is contained in:
parent
35de6bd6d3
commit
70154f1b1d
@ -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
jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
Normal file
344
jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 ) {
|
||||||
|
}
|
||||||
|
}
|
@ -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 );
|
||||||
|
}
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user