org.jadira.reflection.access.model.ClassModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cloning Show documentation
Show all versions of cloning Show documentation
Cloning for Jadira Framework
package org.jadira.reflection.access.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import org.jadira.reflection.access.api.ClassAccess;
import org.jadira.reflection.cloning.annotation.Cloner;
import org.jadira.reflection.cloning.annotation.Flat;
import org.jadira.reflection.cloning.annotation.Immutable;
import org.jadira.reflection.cloning.annotation.NonCloneable;
import org.jadira.reflection.cloning.api.CloneImplementor;
import org.jadira.reflection.cloning.implementor.reflection.CopyConstructorImplementor;
import org.jadira.reflection.cloning.implementor.reflection.ReflectionMethodImplementor;
import org.jadira.reflection.cloning.mutability.MutabilityDetector;
import org.jadira.reflection.core.misc.ClassUtils;
import org.jadira.reflection.core.platform.FeatureDetection;
import org.mutabilitydetector.checkers.MutabilityAnalysisException;
/**
* Provides a base model resulting from introspection of a class
*/
public class ClassModel {
private static final boolean MUTABILITY_DETECTOR_AVAILABLE = FeatureDetection.hasMutabilityDetector();
private static final Class JSR305_IMMUTABLE_ANNOTATION;
private static final ConcurrentHashMap> classModels = new ConcurrentHashMap>(16);
private static final Object MONITOR = new Object();
static {
Class immutableAnnotation;
try {
@SuppressWarnings("unchecked")
final Class myImmutableAnnotation = (Class) Class.forName("javax.annotation.concurrent.Immutable");
immutableAnnotation = myImmutableAnnotation;
} catch (ClassNotFoundException e) {
immutableAnnotation = null;
}
JSR305_IMMUTABLE_ANNOTATION = immutableAnnotation;
}
private final Class> modelClass;
private final boolean detectedAsImmutable;
private final boolean nonCloneable;
private final boolean flat;
private final CloneImplementor cloneImplementor;
private final ClassAccess classAccess;
private FieldModel[] modelFields;
private ClassModel super C> superClassModel;
/**
* Returns a class model for the given ClassAccess instance. If a ClassModel
* already exists, it will be reused.
* @param classAccess The ClassAccess
* @param The type of class
* @return The Field Model
*/
@SuppressWarnings("unchecked")
public static final ClassModel get(ClassAccess classAccess) {
Class> clazz = classAccess.getType();
String classModelKey = (classAccess.getClass().getName() + ":" + clazz.getName());
ClassModel classModel = (ClassModel)classModels.get(classModelKey);
if (classModel != null) {
return classModel;
}
synchronized(MONITOR) {
classModel = (ClassModel)classModels.get(classModelKey);
if (classModel != null) {
return classModel;
} else {
classModel = new ClassModel(classAccess);
classModels.put(classModelKey, classModel);
return classModel;
}
}
}
private ClassModel(ClassAccess classAccess) {
this.classAccess = classAccess;
this.modelClass = classAccess.getType();
boolean myDetectedAsImmutable = false;
try {
if (((modelClass == Object.class) || modelClass.getAnnotation(Immutable.class) != null) || (JSR305_IMMUTABLE_ANNOTATION != null && modelClass.getAnnotation(JSR305_IMMUTABLE_ANNOTATION) != null)
|| (MUTABILITY_DETECTOR_AVAILABLE && MutabilityDetector.getMutabilityDetector().isImmutable(modelClass))) {
myDetectedAsImmutable = true;
}
} catch (MutabilityAnalysisException e) {
}
this.detectedAsImmutable = myDetectedAsImmutable;
Method clonerMethod = null;
Constructor> clonerConstructor = null;
for (Method m : modelClass.getDeclaredMethods()) {
if (m.getAnnotation(Cloner.class) != null) {
if (clonerMethod != null) {
throw new IllegalStateException("Only one cloner method may be declared on a class");
} else {
clonerMethod = m;
}
}
}
Constructor> c = null;
try {
c = modelClass.getConstructor(modelClass);
} catch (NoSuchMethodException e) {
// Ignore
} catch (SecurityException e) {
// Ignore
}
if (c != null && (c.getAnnotation(Cloner.class) != null)) {
if (clonerMethod != null) {
throw new IllegalStateException("Only one cloner method may be declared on a class");
} else {
clonerConstructor = c;
}
}
if (clonerMethod != null) {
this.cloneImplementor = new ReflectionMethodImplementor(clonerMethod);
} else if (clonerConstructor != null) {
this.cloneImplementor = new CopyConstructorImplementor(clonerConstructor);
} else {
cloneImplementor = null;
}
this.nonCloneable = modelClass.getAnnotation(NonCloneable.class) != null;
this.flat = modelClass.getAnnotation(Flat.class) != null;
Field[] fields = ClassUtils.collectDeclaredInstanceFields(modelClass);
@SuppressWarnings("unchecked")
final FieldModel[] myModelFields = (FieldModel[])new FieldModel[fields.length];
for (int i=0; i < fields.length; i++) {
myModelFields[i] = FieldModel.get(fields[i], classAccess.getDeclaredFieldAccess(fields[i]));
}
modelFields = myModelFields;
ClassAccess super C> superClassAccess = classAccess.getSuperClassAccess();
if (superClassAccess != null) {
superClassModel = ClassModel.get(superClassAccess);
}
}
/**
* Access the Class associated with the ClassModel
* @return The associated Class
*/
public Class> getModelClass() {
return modelClass;
}
/**
* Access the ClassAccess associated with the ClassModel
* @return The associated ClassAccess.
*/
public ClassAccess getClassAccess() {
return classAccess;
}
/**
* Access the model for the super class
* @return The associated ClassModel
*/
public ClassModel super C> getSuperClassModel() {
return superClassModel;
}
/**
* Indicates whether the class has been determined to be immutable
* @return True if detected as immutable
*/
public boolean isDetectedAsImmutable() {
return detectedAsImmutable;
}
/**
* Indicates whether the class should be treated as Non-Cloneable. Such a class should not
* be cloned - i.e. the same instance should be returned (as with immutable objects)
* @return True if detected as non-cloneable
*/
public boolean isNonCloneable() {
return nonCloneable;
}
/**
* Indicates whether the class should be treated as Flat. When a Flat class is encountered,
* references are no longer tracked as it is assumed that any reference only appears once.
* This allows a significant increase in performance
* @return True if detected as flat
*/
public boolean isFlat() {
return flat;
}
/**
* If there is a method or constructor configured as a @Cloner instance, the CloneImplementor
* that can invoke this method will be returned
* @return The configured CloneImplementor or null
*/
public CloneImplementor getCloneImplementor() {
return cloneImplementor;
}
/**
* Return an array of FieldModel for the class - one entry per Field
* @return The FieldModels for the class
*/
public FieldModel[] getModelFields() {
return modelFields;
}
}