@ -37,6 +37,8 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method ;
import java.util.HashMap ;
import java.util.IdentityHashMap ;
import java.util.logging.Logger ;
import java.util.logging.Level ;
import java.util.Map ;
import java.util.concurrent.ConcurrentHashMap ;
@ -49,7 +51,7 @@ import java.util.concurrent.ConcurrentHashMap;
* < p > By default , objects that do not implement JmeCloneable will
* be treated like normal Java Cloneable objects . If the object does
* not implement the JmeCloneable or the regular JDK Cloneable interfaces
* AND has no special handling defined then an IllegalArgumentException
* AND has no special handling defined then an IllegalArgumentException
* will be thrown . < / p >
*
* < p > Enhanced object cloning is done in a two step process . First ,
@ -60,7 +62,7 @@ import java.util.concurrent.ConcurrentHashMap;
* can easily have a regular shallow clone implementation just like any
* normal Java objects . Second , the deep cloning of fields happens after
* creation wich means that the clone is available to future field cloning
* to resolve circular references . < / p >
* to resolve circular references . < / p >
*
* < p > Similar to Java serialization , the handling of specific object
* types can be customized . This allows certain objects to be cloned gracefully
@ -87,31 +89,33 @@ import java.util.concurrent.ConcurrentHashMap;
* Foo fooClone = cloner . clone ( foo ) ;
* cloner . clearIndex ( ) ; // prepare it for reuse
* Foo fooClone2 = cloner . clone ( foo ) ;
*
*
* // Example 2: using the utility method that self-instantiates a temporary cloner.
* Foo fooClone = Cloner . deepClone ( foo ) ;
*
*
* < / pre >
*
* @author Paul Speed
* /
public class Cloner {
static Logger log = Logger . getLogger ( Cloner . class . getName ( ) ) ;
/ * *
* Keeps track of the objects that have been cloned so far .
* /
* /
private IdentityHashMap < Object , Object > index = new IdentityHashMap < Object , Object > ( ) ;
/ * *
* Custom functions for cloning objects .
* /
private Map < Class , CloneFunction > functions = new HashMap < Class , CloneFunction > ( ) ;
/ * *
* Cache the clone methods once for all cloners .
* /
* /
private static final Map < Class , Method > methodCache = new ConcurrentHashMap < > ( ) ;
/ * *
* Creates a new cloner with only default clone functions and an empty
* object index .
@ -121,41 +125,41 @@ public class Cloner {
ListCloneFunction listFunction = new ListCloneFunction ( ) ;
functions . put ( java . util . ArrayList . class , listFunction ) ;
functions . put ( java . util . LinkedList . class , listFunction ) ;
functions . put ( java . util . concurrent . CopyOnWriteArrayList . class , listFunction ) ;
functions . put ( java . util . concurrent . CopyOnWriteArrayList . class , listFunction ) ;
functions . put ( java . util . Vector . class , listFunction ) ;
functions . put ( java . util . Stack . class , listFunction ) ;
functions . put ( com . jme3 . util . SafeArrayList . class , listFunction ) ;
}
/ * *
* Convenience utility function that creates a new Cloner , uses it to
* deep clone the object , and then returns the result .
* /
public static < T > T deepClone ( T object ) {
return new Cloner ( ) . clone ( object ) ;
}
}
/ * *
* Deeps clones the specified object , reusing previous clones when possible .
*
*
* < p > Object cloning priority works as follows : < / p >
* < ul >
* < li > If the object has already been cloned then its clone is returned .
* < li > If there is a custom CloneFunction then it is called to clone the object .
* < li > If the object implements Cloneable then its clone ( ) method is called , arrays are
* < li > If the object implements Cloneable then its clone ( ) method is called , arrays are
* deep cloned with entries passing through clone ( ) .
* < li > If the object implements JmeCloneable then its cloneFields ( ) method is called on the
* clone .
* < li > Else an IllegalArgumentException is thrown .
* < li > Else an IllegalArgumentException is thrown .
* < / ul >
*
* Note : objects returned by this method may not have yet had their cloneField ( )
* method called .
* /
* /
public < T > T clone ( T object ) {
return clone ( object , true ) ;
}
/ * *
* Internal method to work around a Java generics typing issue by
* isolating the ' bad ' case into a method with suppressed warnings .
@ -167,20 +171,20 @@ public class Cloner {
// Wrapping it in a method at least isolates the warning suppression
return ( Class < T > ) object . getClass ( ) ;
}
/ * *
* Deeps clones the specified object , reusing previous clones when possible .
*
*
* < p > Object cloning priority works as follows : < / p >
* < ul >
* < li > If the object has already been cloned then its clone is returned .
* < li > If useFunctions is true and there is a custom CloneFunction then it is
* < li > If useFunctions is true and there is a custom CloneFunction then it is
* called to clone the object .
* < li > If the object implements Cloneable then its clone ( ) method is called , arrays are
* < li > If the object implements Cloneable then its clone ( ) method is called , arrays are
* deep cloned with entries passing through clone ( ) .
* < li > If the object implements JmeCloneable then its cloneFields ( ) method is called on the
* clone .
* < li > Else an IllegalArgumentException is thrown .
* < li > Else an IllegalArgumentException is thrown .
* < / ul >
*
* < p > The abililty to selectively use clone functions is useful when
@ -188,71 +192,94 @@ public class Cloner {
*
* Note : objects returned by this method may not have yet had their cloneField ( )
* method called .
* /
* /
public < T > T clone ( T object , boolean useFunctions ) {
if ( object = = null ) {
return null ;
}
Class < T > type = objectClass ( object ) ;
if ( log . isLoggable ( Level . FINER ) ) {
log . finer ( "cloning:" + object . getClass ( ) + "@" + System . identityHashCode ( object ) ) ;
}
Class < T > type = objectClass ( object ) ;
// Check the index to see if we already have it
Object clone = index . get ( object ) ;
if ( clone ! = null ) {
return type . cast ( clone ) ;
if ( log . isLoggable ( Level . FINER ) ) {
log . finer ( "cloned:" + object . getClass ( ) + "@" + System . identityHashCode ( object )
+ " as cached:" + clone . getClass ( ) + "@" + System . identityHashCode ( clone ) ) ;
}
return type . cast ( clone ) ;
}
// See if there is a custom function... that trumps everything.
CloneFunction < T > f = getCloneFunction ( type ) ;
CloneFunction < T > f = getCloneFunction ( type ) ;
if ( f ! = null ) {
T result = f . cloneObject ( this , object ) ;
// Store the object in the identity map so that any circular references
// are resolvable.
index . put ( object , result ) ;
// are resolvable.
index . put ( object , result ) ;
// Now call the function again to deep clone the fields
f . cloneFields ( this , result , object ) ;
return result ;
if ( log . isLoggable ( Level . FINER ) ) {
if ( result = = null ) {
log . finer ( "cloned:" + object . getClass ( ) + "@" + System . identityHashCode ( object )
+ " as transformed:null" ) ;
} else {
log . finer ( "clone:" + object . getClass ( ) + "@" + System . identityHashCode ( object )
+ " as transformed:" + result . getClass ( ) + "@" + System . identityHashCode ( result ) ) ;
}
}
return result ;
}
if ( object . getClass ( ) . isArray ( ) ) {
// Perform an array clone
// Perform an array clone
clone = arrayClone ( object ) ;
// Array clone already indexes the clone
} else if ( object instanceof JmeCloneable ) {
// Use the two-step cloning semantics
clone = ( ( JmeCloneable ) object ) . jmeClone ( ) ;
// Store the object in the identity map so that any circular references
// are resolvable
index . put ( object , clone ) ;
index . put ( object , clone ) ;
( ( JmeCloneable ) clone ) . cloneFields ( this , object ) ;
} else if ( object instanceof Cloneable ) {
// Perform a regular Java shallow clone
try {
clone = javaClone ( object ) ;
} catch ( CloneNotSupportedException e ) {
throw new IllegalArgumentException ( "Object is not cloneable, type:" + type , e ) ;
}
// Store the object in the identity map so that any circular references
// are resolvable
index . put ( object , clone ) ;
index . put ( object , clone ) ;
} else {
throw new IllegalArgumentException ( "Object is not cloneable, type:" + type ) ;
}
if ( log . isLoggable ( Level . FINER ) ) {
log . finer ( "cloned:" + object . getClass ( ) + "@" + System . identityHashCode ( object )
+ " as " + clone . getClass ( ) + "@" + System . identityHashCode ( clone ) ) ;
}
return type . cast ( clone ) ;
}
/ * *
* Sets a custom CloneFunction for the exact Java type . Note : no inheritence
* checks are performed so a function must be registered for each specific type
* that it handles . By default ListCloneFunction is registered for
* that it handles . By default ListCloneFunction is registered for
* ArrayList , LinkedList , CopyOnWriteArrayList , Vector , Stack , and JME ' s SafeArrayList .
* /
public < T > void setCloneFunction ( Class < T > type , CloneFunction < T > function ) {
@ -262,24 +289,45 @@ public class Cloner {
functions . put ( type , function ) ;
}
}
/ * *
* Returns a previously registered clone function for the specified type or null
* if there is no custom clone function for the type .
* /
* /
@SuppressWarnings ( "unchecked" )
public < T > CloneFunction < T > getCloneFunction ( Class < T > type ) {
return ( CloneFunction < T > ) functions . get ( type ) ;
}
return ( CloneFunction < T > ) functions . get ( type ) ;
}
/ * *
* Forces an object to be added to the indexing cache such that attempts
* to clone the ' original ' will always result in the ' clone ' being returned .
* This can be used to stub out specific values from being cloned or to
* force global shared instances to be used even if the object is cloneable
* normally .
* /
public < T > void setClonedValue ( T original , T clone ) {
index . put ( original , clone ) ;
}
/ * *
* Returns true if the specified object has already been cloned
* by this cloner during this session . Cloned objects are cached
* for later use and it ' s sometimes convenient to know if some
* objects have already been cloned .
* /
public boolean isCloned ( Object o ) {
return index . containsKey ( o ) ;
}
/ * *
* Clears the object index allowing the cloner to be reused for a brand new
* cloning operation .
* /
public void clearIndex ( ) {
index . clear ( ) ;
}
}
/ * *
* Performs a raw shallow Java clone using reflection . This call does NOT
* check against the clone index and so will return new objects every time
@ -287,51 +335,51 @@ public class Cloner {
* not ever , depending on the caller ) get resolved .
*
* < p > This method is provided as a convenient way for CloneFunctions to call
* clone ( ) and objects without necessarily knowing their real type . < / p >
* /
* clone ( ) and objects without necessarily knowing their real type . < / p >
* /
public < T > T javaClone ( T object ) throws CloneNotSupportedException {
Method m = methodCache . get ( object . getClass ( ) ) ;
if ( m = = null ) {
try {
// Lookup the method and cache it
m = object . getClass ( ) . getMethod ( "clone" ) ;
} catch ( NoSuchMethodException e ) {
} catch ( NoSuchMethodException e ) {
throw new CloneNotSupportedException ( "No public clone method found for:" + object . getClass ( ) ) ;
}
methodCache . put ( object . getClass ( ) , m ) ;
// Note: yes we might cache the method twice... but so what?
}
try {
Class < ? extends T > type = objectClass ( object ) ;
Class < ? extends T > type = objectClass ( object ) ;
return type . cast ( m . invoke ( object ) ) ;
} catch ( IllegalAccessException | InvocationTargetException e ) {
throw new RuntimeException ( "Error cloning object of type:" + object . getClass ( ) , e ) ;
}
}
}
/ * *
* Clones a primitive array by coping it and clones an object
* array by coping it and then running each of its values through
* Cloner . clone ( ) .
* /
protected < T > T arrayClone ( T object ) {
// Java doesn't support the cloning of arrays through reflection unless
// you open access to Object's protected clone array... which requires
// elevated privileges. So we will do a work-around that is slightly less
// elegant.
// This should be 100% allowed without a case but Java generics
// is not that smart
Class < T > type = objectClass ( object ) ;
Class < T > type = objectClass ( object ) ;
Class elementType = type . getComponentType ( ) ;
int size = Array . getLength ( object ) ;
int size = Array . getLength ( object ) ;
Object clone = Array . newInstance ( elementType , size ) ;
// Store the clone for later lookups
index . put ( object , clone ) ;
index . put ( object , clone ) ;
if ( elementType . isPrimitive ( ) ) {
// Then our job is a bit easier
System . arraycopy ( object , 0 , clone , 0 , size ) ;
@ -341,8 +389,8 @@ public class Cloner {
Object element = clone ( Array . get ( object , i ) ) ;
Array . set ( clone , i , element ) ;
}
}
}
return type . cast ( clone ) ;
}
}