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<C> {
private static final boolean MUTABILITY_DETECTOR_AVAILABLE = FeatureDetection.hasMutabilityDetector();
private static final Class<Annotation> JSR305_IMMUTABLE_ANNOTATION;
private static final ConcurrentHashMap<String, ClassModel<?>> classModels = new ConcurrentHashMap<String, ClassModel<?>>(16);
private static final Object MONITOR = new Object();
static {
Class<Annotation> immutableAnnotation;
try {
@SuppressWarnings("unchecked")
final Class<Annotation> myImmutableAnnotation = (Class<Annotation>) 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<C> classAccess;
private FieldModel<C>[] 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 <C> The type of class
* @return The Field Model
*/
@SuppressWarnings("unchecked")
public static final <C> ClassModel<C> get(ClassAccess<C> classAccess) {
Class<?> clazz = classAccess.getType();
String classModelKey = (classAccess.getClass().getName() + ":" + clazz.getName());
ClassModel<C> classModel = (ClassModel<C>)classModels.get(classModelKey);
if (classModel != null) {
return classModel;
}
synchronized(MONITOR) {
classModel = (ClassModel<C>)classModels.get(classModelKey);
if (classModel != null) {
return classModel;
} else {
classModel = new ClassModel<C>(classAccess);
classModels.put(classModelKey, classModel);
return classModel;
}
}
}
private ClassModel(ClassAccess<C> 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<C>[] myModelFields = (FieldModel<C>[])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<C> 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<C>[] getModelFields() {
return modelFields;
}
}