/*
* Copyright 2013 Chris Pheby
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jadira.reflection.cloning;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
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.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.jadira.reflection.cloning.annotation.Immutable;
import org.jadira.reflection.cloning.annotation.NonCloneable;
import org.jadira.reflection.cloning.annotation.Transient;
import org.jadira.reflection.cloning.api.CloneDriver;
import org.jadira.reflection.cloning.api.CloneImplementor;
import org.jadira.reflection.cloning.api.CloneStrategy;
import org.jadira.reflection.cloning.api.Cloner;
import org.jadira.reflection.cloning.collection.FastIdentityHashSet;
import org.jadira.reflection.cloning.implementor.AsmCloneStrategy;
import org.jadira.reflection.cloning.implementor.PortableCloneStrategy;
import org.jadira.reflection.cloning.implementor.UnsafeCloneStrategy;
import org.jadira.reflection.cloning.implementor.types.ArrayListImplementor;
import org.jadira.reflection.cloning.implementor.types.ConcurrentHashMapImplementor;
import org.jadira.reflection.cloning.implementor.types.GregorianCalendarImplementor;
import org.jadira.reflection.cloning.implementor.types.HashMapImplementor;
import org.jadira.reflection.cloning.implementor.types.HashSetImplementor;
import org.jadira.reflection.cloning.implementor.types.LinkedListImplementor;
import org.jadira.reflection.cloning.implementor.types.TreeMapImplementor;
import org.jadira.reflection.core.platform.FeatureDetection;
/**
* This class is for performing deep clones. <br>
*
* Inspired by the Rits Deep Cloning API, this library uses Unsafe to provide maximum performance
* and a variety of extensibility and annotation configuration strategies.
*
* Most users will interact with the cloning library via this class and the {@link Cloner}
* interface. This is the most functional {@link Cloner} implementation built in to Jadira Cloning.
* The alternative, {@link MinimalCloner} is recommended for situations where only basic
* deep-cloning functionality is required and maximum throughput desirable. <br>
*
* BasicCloner is a fully configurable cloning implementation. <br>
*
* The class features two pluggable {@link CloneStrategy}, with implementations available in the
* library. {@link UnsafeCloneStrategy} makes use of sun.misc.Unsafe and delivers fast performance,
* particularly when using Server VMs with Java 6 and later. However, it requires sun.misc.Unsafe to
* be available. VMs providing this class include Oracle and IBM's JVMs. A fully functional Unsafe
* implementation is not available on Android. For platforms where UnsafeCloneStrategy is not
* suitable, {@link PortableCloneStrategy} should be used instead. In general this delivers good,
* but lower performance, giving about half the cloning throughput. Functionality however it is
* identical. If you use the PortableCloneStrategy, Objenesis must be included on the classpath. <br>
*
* There are a variety of ways to customise cloning. This class can be configured with
* {@link CloneImplementor}s which can be implemented to override the normal cloning behaviour for
* particular classes. Using these, must be enabled by setting {@link #useCloneImplementors} if
* required to be used. <br>
*
* Specific classes can be indicated as being immutable and/or non-cloneable - these classes do not
* need to be copied during the clone. Cloning of immutables can be disabled or enabled using
* {@link #setCloneImmutable(boolean)}, whilst non-cloneable classes may never be cloned. <br>
*
* Setting {@link #cloneTransientFields} to false will prevent the cloning of transient fields which
* will be set to their null (or default primitive) values instead of being cloned. <br>
*
* BasicCloner can detect classes that implement {@link java.lang.Cloneable} and invoke their
* clone() method. To enable this function, set {@link #useCloneable}. <br>
*
* Finally, the operation of the class can be customised using annotations.
* {@link org.jadira.reflection.cloning.annotation.Cloneable} annotation can be used to customise the treatment
* of particular classes being cloned. @Cloner can be used to specify a particular method
* within a class to be used to fulfil the clone for that specific class. {@link NonCloneable}
* indicates that a class should not be cloned. Finally {@link Transient} annotation can be used on
* any class field to indicate that the field is transient. In the case of this last annotation, use
* {{@link #cloneTransientAnnotatedFields} to enable or disable the capability (by default these
* fields are not cloned). <br>
*
* {@link Immutable} or {@link javax.annotation.concurrent.Immutable} provide an alternative
* mechanism for indicating that a class is immutable. <br>
*
* If the Mutability Detector library is on the classpath this will automatically be used to
* determine immutability for all classes being cloned. If a class is identified as IMMUTABLE or
* EFFECTIVELY_IMMUTABLE by Mutability Detector it will be treated as immutable.
*
* @see MinimalCloner A most optimised implementation of Cloner that only provides basic
* deep cloning functionality but no configurability.
*/
public class BasicCloner implements Cloner, CloneDriver, CloneImplementor {
private final CloneStrategy cloneStrategy;
private Map<Class<?>, CloneImplementor> builtInImplementors = new IdentityHashMap<Class<?>, CloneImplementor>();
private Map<Class<?>, CloneImplementor> allImplementors = new IdentityHashMap<Class<?>, CloneImplementor>();
private Map<Class<?>, CloneImplementor> annotationImplementors = new IdentityHashMap<Class<?>, CloneImplementor>();
private Map<Class<?>, MethodHandle> cloneMethods = new IdentityHashMap<Class<?>, MethodHandle>();
private Set<Class<?>> immutableClasses = new FastIdentityHashSet<Class<?>>();
private Set<Class<?>> nonCloneableClasses = new FastIdentityHashSet<Class<?>>();
private boolean useCloneable = false;
private boolean useCloneImplementors = true;
private boolean cloneTransientFields = true;
private boolean cloneTransientAnnotatedFields = false;
private boolean cloneImmutable = false;
private boolean cloneSyntheticFields = false;
private boolean trackReferences = true;
private boolean trackReferencesForFlatClasses;
private Map<Class<?>, Object> builtInImmutableInstances = new HashMap<Class<?>, Object>();
private IdentityHashMap<Object, Object> allImmutableInstances = new IdentityHashMap<Object, Object>();
private Object[] allImmutableInstancesArray;
/**
* Create a new instance with {@link UnsafeCloneStrategy}, unless it is not available in which
* case {@link PortableCloneStrategy} will be used.
*/
public BasicCloner() {
if (FeatureDetection.hasUnsafe()) {
this.cloneStrategy = UnsafeCloneStrategy.getInstance();
} else {
this.cloneStrategy = AsmCloneStrategy.getInstance();
}
initialize();
}
/**
* Creates a new instance with the given {@link CloneStrategy}
* @param cloneStrategy CloneStrategy to be used
*/
public BasicCloner(final CloneStrategy cloneStrategy) {
this.cloneStrategy = cloneStrategy;
initialize();
}
/**
* Initialise a new instance
*/
private void initialize() {
initializeBuiltInImplementors();
initializeBuiltInImmutableInstances();
}
/**
* Initialise a set of built in CloneImplementors for commonly used JDK types
*/
private void initializeBuiltInImplementors() {
builtInImplementors.put(ArrayList.class, new ArrayListImplementor());
builtInImplementors.put(ConcurrentHashMap.class, new ConcurrentHashMapImplementor());
builtInImplementors.put(GregorianCalendar.class, new GregorianCalendarImplementor());
builtInImplementors.put(HashMap.class, new HashMapImplementor());
builtInImplementors.put(HashSet.class, new HashSetImplementor());
builtInImplementors.put(LinkedList.class, new LinkedListImplementor());
builtInImplementors.put(TreeMap.class, new TreeMapImplementor());
allImplementors.putAll(builtInImplementors);
}
private void initializeBuiltInImmutableInstances() {
try {
Field field = TreeSet.class.getDeclaredField("PRESENT");
field.setAccessible(true);
Object treeSetPresent = field.get(null);
builtInImmutableInstances.put(TreeSet.class, treeSetPresent);
putImmutableInstance(treeSetPresent);
field = HashSet.class.getDeclaredField("PRESENT");
field.setAccessible(true);
Object hashSetPresent = field.get(null);
builtInImmutableInstances.put(HashSet.class, hashSetPresent);
putImmutableInstance(hashSetPresent);
} catch (final SecurityException e) {
throw new IllegalStateException(e);
} catch (final NoSuchFieldException e) {
throw new IllegalStateException(e);
} catch (final IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (final IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
@Override
public <T> T clone(T obj) {
return clone(obj, this, trackReferences ? new IdentityHashMap<Object, Object>(10) : null, 0L);
}
@Override
public <T> T newInstance(Class<T> c) {
return cloneStrategy.newInstance(c);
}
@Override
public boolean canClone(Class<?> clazz) {
return cloneStrategy.canClone(clazz);
}
@Override
public <T> T clone(T obj, CloneDriver context, IdentityHashMap<Object, Object> referencesToReuse, long stackDepth) {
return cloneStrategy.clone(obj, context, referencesToReuse, stackDepth);
}
@Override
public CloneImplementor getBuiltInImplementor(Class<?> clazz) {
return allImplementors.get(clazz);
}
@Override
public CloneImplementor getImplementor(Class<?> clazz) {
return allImplementors.get(clazz);
}
@Override
public CloneImplementor getAnnotationImplementor(Class<?> clazz) {
return annotationImplementors.get(clazz);
}
/**
* Sets CloneImplementors to be used.
* @param implementors The implementors
*/
public void setImplementors(Map<Class<?>, CloneImplementor> implementors) {
// this.implementors = implementors;
this.allImplementors = new HashMap<Class<?>, CloneImplementor>();
allImplementors.putAll(builtInImplementors);
allImplementors.putAll(implementors);
}
@Override
public Set<Class<?>> getImmutableClasses() {
return immutableClasses;
}
/**
* Indicates classes which are immutable.
* @param immutableClasses Classes which should be treated as immutable
*/
public void setImmutableClasses(Set<Class<?>> immutableClasses) {
this.immutableClasses = immutableClasses;
}
@Override
public Set<Class<?>> getNonCloneableClasses() {
return nonCloneableClasses;
}
/**
* Indicates classes that are not Cloneable.
* @param nonCloneableClasses Set of non-cloneable classes
*/
public void setNonCloneableClasses(Set<Class<?>> nonCloneableClasses) {
this.nonCloneableClasses = nonCloneableClasses;
}
@Override
public boolean isUseCloneable() {
return useCloneable;
}
/**
* If true, the clone() method of classes implementing {@link java.lang.Cloneable} will be
* delegated to where appropriate
* @param useCloneable True if the cloneable method should be used
*/
public void setUseCloneable(boolean useCloneable) {
this.useCloneable = useCloneable;
}
@Override
public MethodHandle getCloneMethod(Class<?> clazz) {
return cloneMethods.get(clazz);
}
@Override
public boolean isCloneTransientFields() {
return cloneTransientFields;
}
/**
* If true, fields marked with the transient keyword should be cloned
* @param cloneTransientFields true if fields decorated with transient keyword should be cloned
*/
public void setCloneTransientFields(boolean cloneTransientFields) {
this.cloneTransientFields = cloneTransientFields;
}
@Override
public boolean isCloneTransientAnnotatedFields() {
return cloneTransientAnnotatedFields;
}
/**
* If true, fields annotated with @Transient should be cloned
* @param cloneTransientAnnotatedFields True if fields annotated with @Transient should be cloned
*/
public void setCloneTransientAnnotatedFields(boolean cloneTransientAnnotatedFields) {
this.cloneTransientAnnotatedFields = cloneTransientAnnotatedFields;
}
@Override
public boolean isCloneImmutable() {
return cloneImmutable;
}
/**
* If true, immutable classes should be cloned.
* @param cloneImmutable True if immutable classes should be cloned.
*/
public void setCloneImmutable(boolean cloneImmutable) {
this.cloneImmutable = cloneImmutable;
}
@Override
public void initialiseFor(Class<?> classes) {
cloneStrategy.initialiseFor(classes);
}
@Override
public boolean isUseCloneImplementors() {
return useCloneImplementors;
}
/**
* If true, cloning may be delegates to clone implementors for specific tasks.
* @param useCloneImplementors True if CloneImplementors may be delegated to
*/
public void setUseCloneImplementors(boolean useCloneImplementors) {
this.useCloneImplementors = useCloneImplementors;
}
@Override
public boolean isCloneSyntheticFields() {
return cloneSyntheticFields;
}
/**
* If true, synthetic fields should be cloned
* @param cloneSyntheticFields True if synthetic fields should be cloned
*/
public void setCloneSyntheticFields(boolean cloneSyntheticFields) {
this.cloneSyntheticFields = cloneSyntheticFields;
}
@Override
public void putAnnotationImplementor(Class<?> clazz, CloneImplementor implementor) {
this.annotationImplementors.put(clazz, implementor);
}
@Override
public void putCloneMethod(Class<?> clazz, MethodHandle handle) {
this.cloneMethods.put(clazz, handle);
}
@Override
public boolean isImmutableInstance(Object instance) {
if (allImmutableInstances != null) {
for (int i = 0; i < allImmutableInstancesArray.length; i++) {
if (allImmutableInstancesArray[i] == instance) {
return true;
}
}
return false;
} else {
return allImmutableInstances.containsKey(instance);
}
}
@Override
public void putImmutableInstance(Object instance) {
this.allImmutableInstances.put(instance, Boolean.TRUE);
Set<Object> keySet = this.allImmutableInstances.keySet();
this.allImmutableInstancesArray = new Object[keySet.size()];
int i = 0;
for (Object next: keySet) {
this.allImmutableInstancesArray[i] = next;
i++;
if (i >= allImmutableInstancesArray.length) {
break;
}
}
}
public boolean isTrackReferences() {
return trackReferences;
}
/**
* If false, do not track references so that objects are reused if they appear twice
* @param trackReferences True if references should be tracked
*/
public void setTrackReferences(boolean trackReferences) {
this.trackReferences = trackReferences;
}
public boolean isTrackReferencesForFlatClasses() {
return trackReferencesForFlatClasses;
}
/**
* If false, do not track references for flat classes when tracking references
* @param trackReferencesForFlatClasses False if references should not be tracked for flat classes
*/
public void setTrackReferencesForFlatClasses(boolean trackReferencesForFlatClasses) {
this.trackReferencesForFlatClasses = trackReferencesForFlatClasses;
}
}