WarpPI/src/com/rits/cloning/Cloner.java
2016-09-02 20:32:37 +02:00

683 lines
19 KiB
Java

package com.rits.cloning;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* Cloner: deep clone objects.
*
* This class is thread safe. One instance can be used by multiple threads on
* the same time.
*
* @author kostantinos.kougios
* 18 Sep 2008
*/
public class Cloner {
private final IInstantiationStrategy instantiationStrategy;
private final Set<Class<?>> ignored = new HashSet<Class<?>>();
private final Set<Class<?>> ignoredInstanceOf = new HashSet<Class<?>>();
private final Set<Class<?>> nullInstead = new HashSet<Class<?>>();
private final Map<Class<?>, IFastCloner> fastCloners = new HashMap<Class<?>, IFastCloner>();
private final Map<Object, Boolean> ignoredInstances = new IdentityHashMap<Object, Boolean>();
private final ConcurrentHashMap<Class<?>, List<Field>> fieldsCache = new ConcurrentHashMap<Class<?>, List<Field>>();
public IDumpCloned getDumpCloned() {
return dumpCloned;
}
/**
* provide a cloned classes dumper (so i.e. they can be logged or stored in
* a file
* instead of the default behaviour which is to println(cloned) )
*
* @param dumpCloned
* an implementation of the interface which can dump the
* cloned classes.
*/
public void setDumpCloned(IDumpCloned dumpCloned) {
this.dumpCloned = dumpCloned;
}
private IDumpCloned dumpCloned = null;
private boolean cloningEnabled = true;
private boolean nullTransient = false;
private boolean cloneSynthetics = true;
public Cloner() {
this.instantiationStrategy = ObjenesisInstantiationStrategy.getInstance();
init();
}
public Cloner(final IInstantiationStrategy instantiationStrategy) {
this.instantiationStrategy = instantiationStrategy;
init();
}
public boolean isNullTransient() {
return nullTransient;
}
/**
* this makes the cloner to set a transient field to null upon cloning.
*
* NOTE: primitive types can't be nulled. Their value will be set to
* default, i.e. 0 for int
*
* @param nullTransient
* true for transient fields to be nulled
*/
public void setNullTransient(final boolean nullTransient) {
this.nullTransient = nullTransient;
}
public void setCloneSynthetics(final boolean cloneSynthetics) {
this.cloneSynthetics = cloneSynthetics;
}
private void init() {
registerKnownJdkImmutableClasses();
registerKnownConstants();
registerFastCloners();
}
/**
* registers a std set of fast cloners.
*/
protected void registerFastCloners() {
fastCloners.put(GregorianCalendar.class, new FastClonerCalendar());
fastCloners.put(ArrayList.class, new FastClonerArrayList());
fastCloners.put(LinkedList.class, new FastClonerLinkedList());
fastCloners.put(HashSet.class, new FastClonerHashSet());
fastCloners.put(HashMap.class, new FastClonerHashMap());
fastCloners.put(TreeMap.class, new FastClonerTreeMap());
fastCloners.put(ConcurrentHashMap.class, new FastClonerConcurrentHashMap());
}
private IDeepCloner deepCloner = new IDeepCloner() {
@Override
public <T> T deepClone(T o, Map<Object, Object> clones) {
try {
return cloneInternal(o, clones);
} catch (IllegalAccessException e) {
// just rethrow unchecked
throw new IllegalStateException(e);
}
}
};
protected Object fastClone(final Object o, final Map<Object, Object> clones) throws IllegalAccessException {
final Class<? extends Object> c = o.getClass();
final IFastCloner fastCloner = fastCloners.get(c);
if (fastCloner != null)
return fastCloner.clone(o, deepCloner, clones);
return null;
}
public void registerConstant(final Object o) {
ignoredInstances.put(o, true);
}
public void registerConstant(final Class<?> c, final String privateFieldName) {
try {
final Field field = c.getDeclaredField(privateFieldName);
field.setAccessible(true);
final Object v = field.get(null);
ignoredInstances.put(v, true);
} catch (final SecurityException e) {
throw new RuntimeException(e);
} catch (final NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (final IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* registers some known JDK immutable classes. Override this to register
* your
* own list of jdk's immutable classes
*/
protected void registerKnownJdkImmutableClasses() {
registerImmutable(String.class);
registerImmutable(Integer.class);
registerImmutable(Long.class);
registerImmutable(Boolean.class);
registerImmutable(Class.class);
registerImmutable(Float.class);
registerImmutable(Double.class);
registerImmutable(Character.class);
registerImmutable(Byte.class);
registerImmutable(Short.class);
registerImmutable(Void.class);
registerImmutable(BigDecimal.class);
registerImmutable(BigInteger.class);
registerImmutable(URI.class);
registerImmutable(URL.class);
registerImmutable(UUID.class);
registerImmutable(Pattern.class);
}
protected void registerKnownConstants() {
// registering known constants of the jdk.
registerStaticFields(TreeSet.class, HashSet.class, HashMap.class, TreeMap.class);
}
/**
* registers all static fields of these classes. Those static fields won't
* be cloned when an instance
* of the class is cloned.
*
* This is useful i.e. when a static field object is added into maps or
* sets. At that point, there is no
* way for the cloner to know that it was static except if it is registered.
*
* @param classes
* array of classes
*/
public void registerStaticFields(final Class<?>... classes) {
for (final Class<?> c : classes) {
final List<Field> fields = allFields(c);
for (final Field field : fields) {
final int mods = field.getModifiers();
if (Modifier.isStatic(mods) && !field.getType().isPrimitive()) {
registerConstant(c, field.getName());
}
}
}
}
/**
* spring framework friendly version of registerStaticFields
*
* @param set
* a set of classes which will be scanned for static fields
*/
public void setExtraStaticFields(final Set<Class<?>> set) {
registerStaticFields((Class<?>[]) set.toArray());
}
/**
* instances of classes that shouldn't be cloned can be registered using
* this method.
*
* @param c
* The class that shouldn't be cloned. That is, whenever a deep
* clone for
* an object is created and c is encountered, the object instance
* of c will
* be added to the clone.
*/
public void dontClone(final Class<?>... c) {
for (final Class<?> cl : c) {
ignored.add(cl);
}
}
public void dontCloneInstanceOf(final Class<?>... c) {
for (final Class<?> cl : c) {
ignoredInstanceOf.add(cl);
}
}
public void setDontCloneInstanceOf(final Class<?>... c) {
dontCloneInstanceOf(c);
}
/**
* instead of cloning these classes will set the field to null
*
* @param c
* the classes to nullify during cloning
*/
public void nullInsteadOfClone(final Class<?>... c) {
for (final Class<?> cl : c) {
nullInstead.add(cl);
}
}
// spring framework friendly version of nullInsteadOfClone
public void setExtraNullInsteadOfClone(final Set<Class<?>> set) {
nullInstead.addAll(set);
}
/**
* registers an immutable class. Immutable classes are not cloned.
*
* @param c
* the immutable class
*/
public void registerImmutable(final Class<?>... c) {
for (final Class<?> cl : c) {
ignored.add(cl);
}
}
// spring framework friendly version of registerImmutable
public void setExtraImmutables(final Set<Class<?>> set) {
ignored.addAll(set);
}
public void registerFastCloner(final Class<?> c, final IFastCloner fastCloner) {
if (fastCloners.containsKey(c))
throw new IllegalArgumentException(c + " already fast-cloned!");
fastCloners.put(c, fastCloner);
}
public void unregisterFastCloner(final Class<?> c) {
fastCloners.remove(c);
}
/**
* creates a new instance of c. Override to provide your own implementation
*
* @param <T>
* the type of c
* @param c
* the class
* @return a new instance of c
*/
protected <T> T newInstance(final Class<T> c) {
return instantiationStrategy.newInstance(c);
}
@SuppressWarnings("unchecked")
public <T> T fastCloneOrNewInstance(final Class<T> c) {
try {
final T fastClone = (T) fastClone(c, null);
if (fastClone != null)
return fastClone;
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
return newInstance(c);
}
/**
* deep clones "o".
*
* @param <T>
* the type of "o"
* @param o
* the object to be deep-cloned
* @return a deep-clone of "o".
*/
public <T> T deepClone(final T o) {
if (o == null)
return null;
if (!cloningEnabled)
return o;
if (dumpCloned != null) {
dumpCloned.startCloning(o.getClass());
}
final Map<Object, Object> clones = new IdentityHashMap<Object, Object>(16);
try {
return cloneInternal(o, clones);
} catch (final IllegalAccessException e) {
throw new CloningException("error during cloning of " + o, e);
}
}
public <T> T deepCloneDontCloneInstances(final T o, final Object... dontCloneThese) {
if (o == null)
return null;
if (!cloningEnabled)
return o;
if (dumpCloned != null) {
dumpCloned.startCloning(o.getClass());
}
final Map<Object, Object> clones = new IdentityHashMap<Object, Object>(16);
for (final Object dc : dontCloneThese) {
clones.put(dc, dc);
}
try {
return cloneInternal(o, clones);
} catch (final IllegalAccessException e) {
throw new CloningException("error during cloning of " + o, e);
}
}
/**
* shallow clones "o". This means that if c=shallowClone(o) then
* c!=o. Any change to c won't affect o.
*
* @param <T>
* the type of o
* @param o
* the object to be shallow-cloned
* @return a shallow clone of "o"
*/
public <T> T shallowClone(final T o) {
if (o == null)
return null;
if (!cloningEnabled)
return o;
try {
return cloneInternal(o, null);
} catch (final IllegalAccessException e) {
throw new CloningException("error during cloning of " + o, e);
}
}
// caches immutables for quick reference
private final ConcurrentHashMap<Class<?>, Boolean> immutables = new ConcurrentHashMap<Class<?>, Boolean>();
private boolean cloneAnonymousParent = true;
/**
* override this to decide if a class is immutable. Immutable classes are
* not cloned.
*
* @param clz
* the class under check
* @return true to mark clz as immutable and skip cloning it
*/
protected boolean considerImmutable(final Class<?> clz) {
return false;
}
protected Class<?> getImmutableAnnotation() {
return Immutable.class;
}
/**
* decides if a class is to be considered immutable or not
*
* @param clz
* the class under check
* @return true if the clz is considered immutable
*/
private boolean isImmutable(final Class<?> clz) {
final Boolean isIm = immutables.get(clz);
if (isIm != null)
return isIm;
if (considerImmutable(clz))
return true;
final Class<?> immutableAnnotation = getImmutableAnnotation();
for (final Annotation annotation : clz.getDeclaredAnnotations()) {
if (annotation.annotationType() == immutableAnnotation) {
immutables.put(clz, Boolean.TRUE);
return true;
}
}
Class<?> c = clz.getSuperclass();
while (c != null && c != Object.class) {
for (final Annotation annotation : c.getDeclaredAnnotations()) {
if (annotation.annotationType() == Immutable.class) {
final Immutable im = (Immutable) annotation;
if (im.subClass()) {
immutables.put(clz, Boolean.TRUE);
return true;
}
}
}
c = c.getSuperclass();
}
immutables.put(clz, Boolean.FALSE);
return false;
}
@SuppressWarnings("unchecked")
protected <T> T cloneInternal(final T o, final Map<Object, Object> clones) throws IllegalAccessException {
if (o == null)
return null;
if (o == this)
return null; // don't clone the cloner!
if (ignoredInstances.containsKey(o))
return o;
if (o instanceof Enum)
return o;
final Class<T> clz = (Class<T>) o.getClass();
// skip cloning ignored classes
if (nullInstead.contains(clz))
return null;
if (ignored.contains(clz))
return o;
for (final Class<?> iClz : ignoredInstanceOf) {
if (iClz.isAssignableFrom(clz))
return o;
}
if (isImmutable(clz))
return o;
if (o instanceof IFreezable) {
final IFreezable f = (IFreezable) o;
if (f.isFrozen())
return o;
}
final Object clonedPreviously = clones != null ? clones.get(o) : null;
if (clonedPreviously != null)
return (T) clonedPreviously;
final Object fastClone = fastClone(o, clones);
if (fastClone != null) {
if (clones != null) {
clones.put(o, fastClone);
}
return (T) fastClone;
}
if (dumpCloned != null) {
dumpCloned.startCloning(o.getClass());
}
if (clz.isArray()) {
return cloneArray(o, clones);
}
return cloneObject(o, clones, clz);
}
// clones o, no questions asked!
private <T> T cloneObject(T o, Map<Object, Object> clones, Class<T> clz) throws IllegalAccessException {
final T newInstance = newInstance(clz);
if (clones != null) {
clones.put(o, newInstance);
}
final List<Field> fields = allFields(clz);
for (final Field field : fields) {
final int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers)) {
if (!(nullTransient && Modifier.isTransient(modifiers))) {
// request by Jonathan : transient fields can be null-ed
final Object fieldObject = field.get(o);
final boolean shouldClone = (cloneSynthetics || !field.isSynthetic()) && (cloneAnonymousParent || !isAnonymousParent(field));
final Object fieldObjectClone = clones != null ? (shouldClone ? cloneInternal(fieldObject, clones) : fieldObject) : fieldObject;
field.set(newInstance, fieldObjectClone);
if (dumpCloned != null && fieldObjectClone != fieldObject) {
dumpCloned.cloning(field, o.getClass());
}
}
}
}
return newInstance;
}
@SuppressWarnings("unchecked")
private <T> T cloneArray(T o, Map<Object, Object> clones) throws IllegalAccessException {
final Class<T> clz = (Class<T>) o.getClass();
final int length = Array.getLength(o);
final T newInstance = (T) Array.newInstance(clz.getComponentType(), length);
if (clones != null) {
clones.put(o, newInstance);
}
if (clz.getComponentType().isPrimitive() || isImmutable(clz.getComponentType())) {
System.arraycopy(o, 0, newInstance, 0, length);
} else {
for (int i = 0; i < length; i++) {
final Object v = Array.get(o, i);
final Object clone = clones != null ? cloneInternal(v, clones) : v;
Array.set(newInstance, i, clone);
}
}
return newInstance;
}
private boolean isAnonymousParent(final Field field) {
return "this$0".equals(field.getName());
}
/**
* copies all properties from src to dest. Src and dest can be of different
* class, provided they contain same field names/types
*
* @param src
* the source object
* @param dest
* the destination object which must contain as minimum all the
* fields of src
*/
public <T, E extends T> void copyPropertiesOfInheritedClass(final T src, final E dest) {
if (src == null)
throw new IllegalArgumentException("src can't be null");
if (dest == null)
throw new IllegalArgumentException("dest can't be null");
final Class<? extends Object> srcClz = src.getClass();
final Class<? extends Object> destClz = dest.getClass();
if (srcClz.isArray()) {
if (!destClz.isArray())
throw new IllegalArgumentException("can't copy from array to non-array class " + destClz);
final int length = Array.getLength(src);
for (int i = 0; i < length; i++) {
final Object v = Array.get(src, i);
Array.set(dest, i, v);
}
return;
}
final List<Field> fields = allFields(srcClz);
final List<Field> destFields = allFields(dest.getClass());
for (final Field field : fields) {
if (!Modifier.isStatic(field.getModifiers())) {
try {
final Object fieldObject = field.get(src);
field.setAccessible(true);
if (destFields.contains(field)) {
field.set(dest, fieldObject);
}
} catch (final IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* reflection utils
*/
private void addAll(final List<Field> l, final Field[] fields) {
for (final Field field : fields) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
l.add(field);
}
}
/**
* reflection utils, override this to choose which fields to clone
*/
protected List<Field> allFields(final Class<?> c) {
List<Field> l = fieldsCache.get(c);
if (l == null) {
l = new LinkedList<Field>();
final Field[] fields = c.getDeclaredFields();
addAll(l, fields);
Class<?> sc = c;
while ((sc = sc.getSuperclass()) != Object.class && sc != null) {
addAll(l, sc.getDeclaredFields());
}
fieldsCache.putIfAbsent(c, l);
}
return l;
}
public boolean isDumpClonedClasses() {
return dumpCloned != null;
}
/**
* will println() all cloned classes. Useful for debugging only. Use
* setDumpCloned() if you want to control where to print the cloned
* classes.
*
* @param dumpClonedClasses
* true to enable printing all cloned classes
*/
public void setDumpClonedClasses(final boolean dumpClonedClasses) {
if (dumpClonedClasses) {
dumpCloned = new IDumpCloned() {
@Override
public void startCloning(Class<?> clz) {
System.out.println("clone>" + clz);
}
@Override
public void cloning(Field field, Class<?> clz) {
System.out.println("cloned field>" + field + " -- of class " + clz);
}
};
} else
dumpCloned = null;
}
public boolean isCloningEnabled() {
return cloningEnabled;
}
public void setCloningEnabled(final boolean cloningEnabled) {
this.cloningEnabled = cloningEnabled;
}
/**
* if false, anonymous classes parent class won't be cloned. Default is true
*/
public void setCloneAnonymousParent(final boolean cloneAnonymousParent) {
this.cloneAnonymousParent = cloneAnonymousParent;
}
public boolean isCloneAnonymousParent() {
return cloneAnonymousParent;
}
/**
* @return a standard cloner instance, will do for most use cases
*/
public static Cloner standard() {
return new Cloner();
}
/**
* @return if Cloner lib is in a shared jar folder for a container (i.e.
* tomcat/shared), then
* this method is preferable in order to instantiate cloner. Please
* see https://code.google.com/p/cloning/issues/detail?id=23
*/
public static Cloner shared() {
return new Cloner(new ObjenesisInstantiationStrategy());
}
}