View Javadoc
1   package org.jadira.reflection.access.model;
2   
3   import java.lang.annotation.Annotation;
4   import java.lang.reflect.Constructor;
5   import java.lang.reflect.Field;
6   import java.lang.reflect.Method;
7   import java.util.concurrent.ConcurrentHashMap;
8   
9   import org.jadira.reflection.access.api.ClassAccess;
10  import org.jadira.reflection.cloning.annotation.Cloner;
11  import org.jadira.reflection.cloning.annotation.Flat;
12  import org.jadira.reflection.cloning.annotation.Immutable;
13  import org.jadira.reflection.cloning.annotation.NonCloneable;
14  import org.jadira.reflection.cloning.api.CloneImplementor;
15  import org.jadira.reflection.cloning.implementor.reflection.CopyConstructorImplementor;
16  import org.jadira.reflection.cloning.implementor.reflection.ReflectionMethodImplementor;
17  import org.jadira.reflection.cloning.mutability.MutabilityDetector;
18  import org.jadira.reflection.core.misc.ClassUtils;
19  import org.jadira.reflection.core.platform.FeatureDetection;
20  import org.mutabilitydetector.checkers.MutabilityAnalysisException;
21  
22  /**
23   * Provides a base model resulting from introspection of a class
24   */
25  public class ClassModel<C> {
26  
27  	private static final boolean MUTABILITY_DETECTOR_AVAILABLE = FeatureDetection.hasMutabilityDetector();
28  
29  	private static final Class<Annotation> JSR305_IMMUTABLE_ANNOTATION;
30  
31      private static final ConcurrentHashMap<String, ClassModel<?>> classModels = new ConcurrentHashMap<String, ClassModel<?>>(16);
32      
33  	private static final Object MONITOR = new Object();
34  	
35  	static {	
36  		Class<Annotation> immutableAnnotation;
37  		try {
38  			@SuppressWarnings("unchecked")
39  			final Class<Annotation> myImmutableAnnotation = (Class<Annotation>) Class.forName("javax.annotation.concurrent.Immutable");
40  			immutableAnnotation = myImmutableAnnotation;
41  		} catch (ClassNotFoundException e) {
42  			immutableAnnotation = null;
43  		}
44  		JSR305_IMMUTABLE_ANNOTATION = immutableAnnotation;
45  	}
46  
47  	private final Class<?> modelClass;
48  	private final boolean detectedAsImmutable;
49  	private final boolean nonCloneable;
50  	private final boolean flat;
51  
52  	private final CloneImplementor cloneImplementor;
53  
54  	private final ClassAccess<C> classAccess;
55  
56  	private FieldModel<C>[] modelFields;
57  	
58  	private ClassModel<? super C> superClassModel;
59  
60      /**
61       * Returns a class model for the given ClassAccess instance. If a ClassModel 
62       * already exists, it will be reused.
63       * @param classAccess The ClassAccess
64       * @param <C> The type of class
65       * @return The Field Model
66       */
67  	@SuppressWarnings("unchecked")
68  	public static final <C> ClassModel<C> get(ClassAccess<C> classAccess) {
69          
70  		Class<?> clazz = classAccess.getType();
71  		
72  		String classModelKey = (classAccess.getClass().getName() + ":" + clazz.getName());
73  		
74  		ClassModel<C> classModel = (ClassModel<C>)classModels.get(classModelKey);
75      	if (classModel != null) {       	
76          	return classModel;
77          }
78      	
79      	synchronized(MONITOR) {
80      		classModel = (ClassModel<C>)classModels.get(classModelKey);
81          	if (classModel != null) {       	
82              	return classModel;
83              } else {
84              	classModel = new ClassModel<C>(classAccess);
85              	classModels.put(classModelKey, classModel);
86              	
87              	return classModel;
88              }
89      	}
90      }
91  	
92  	private ClassModel(ClassAccess<C> classAccess) {
93  
94  		this.classAccess = classAccess;
95  		this.modelClass = classAccess.getType();
96  		
97  		boolean myDetectedAsImmutable = false;
98  		try {
99  			if (((modelClass == Object.class) || modelClass.getAnnotation(Immutable.class) != null) || (JSR305_IMMUTABLE_ANNOTATION != null && modelClass.getAnnotation(JSR305_IMMUTABLE_ANNOTATION) != null)
100 					|| (MUTABILITY_DETECTOR_AVAILABLE && MutabilityDetector.getMutabilityDetector().isImmutable(modelClass))) {
101 				myDetectedAsImmutable = true; 
102 			}
103 		} catch (MutabilityAnalysisException e) {
104 		}
105 		this.detectedAsImmutable = myDetectedAsImmutable;
106 
107 		Method clonerMethod = null;
108 		Constructor<?> clonerConstructor = null;
109 		for (Method m : modelClass.getDeclaredMethods()) {
110 			if (m.getAnnotation(Cloner.class) != null) {
111 				if (clonerMethod != null) {
112 					throw new IllegalStateException("Only one cloner method may be declared on a class");
113 				} else {
114 					clonerMethod = m;
115 				}
116 			}
117 		}
118 		Constructor<?> c = null;
119 		try {
120 			c = modelClass.getConstructor(modelClass);
121 		} catch (NoSuchMethodException e) {
122 			// Ignore
123 		} catch (SecurityException e) {
124 			// Ignore
125 		}
126 		if (c != null && (c.getAnnotation(Cloner.class) != null)) {
127 			if (clonerMethod != null) {
128 				throw new IllegalStateException("Only one cloner method may be declared on a class");
129 			} else {
130 				clonerConstructor = c;
131 			}
132 		}
133 
134 		if (clonerMethod != null) {
135 			this.cloneImplementor = new ReflectionMethodImplementor(clonerMethod);
136 		} else if (clonerConstructor != null) {
137 			this.cloneImplementor = new CopyConstructorImplementor(clonerConstructor);
138 		} else {
139 			cloneImplementor = null;
140 		}
141 
142 		this.nonCloneable = modelClass.getAnnotation(NonCloneable.class) != null;
143 
144 		this.flat = modelClass.getAnnotation(Flat.class) != null;
145 		
146 		Field[] fields = ClassUtils.collectDeclaredInstanceFields(modelClass);
147 		
148 		@SuppressWarnings("unchecked")
149 		final FieldModel<C>[] myModelFields = (FieldModel<C>[])new FieldModel[fields.length];
150 		for (int i=0; i < fields.length; i++) {
151 			myModelFields[i] = FieldModel.get(fields[i], classAccess.getDeclaredFieldAccess(fields[i]));
152 		}
153 		modelFields = myModelFields;
154 
155 		ClassAccess<? super C> superClassAccess = classAccess.getSuperClassAccess();
156 		if (superClassAccess != null) {
157 			superClassModel = ClassModel.get(superClassAccess);
158 		}
159 	}
160 	
161 	/**
162 	 * Access the Class associated with the ClassModel
163 	 * @return The associated Class
164 	 */
165 	public Class<?> getModelClass() {
166 		return modelClass;
167 	}
168 
169     /**
170      * Access the ClassAccess associated with the ClassModel
171      * @return The associated ClassAccess.
172      */
173 	public ClassAccess<C> getClassAccess() {
174 		return classAccess;
175 	}
176 	
177 	/**
178 	 * Access the model for the super class
179 	 * @return The associated ClassModel
180 	 */
181 	public ClassModel<? super C> getSuperClassModel() {
182 		return superClassModel;
183 	}
184 
185     /**
186      * Indicates whether the class has been determined to be immutable
187      * @return True if detected as immutable
188      */
189 	public boolean isDetectedAsImmutable() {
190 		return detectedAsImmutable;
191 	}
192 
193     /**
194      * Indicates whether the class should be treated as Non-Cloneable. Such a class should not
195      * be cloned - i.e. the same instance should be returned (as with immutable objects)
196      * @return True if detected as non-cloneable
197      */
198 	public boolean isNonCloneable() {
199 		return nonCloneable;
200 	}
201 
202     /**
203      * Indicates whether the class should be treated as Flat. When a Flat class is encountered,
204      * references are no longer tracked as it is assumed that any reference only appears once.
205      * This allows a significant increase in performance
206      * @return True if detected as flat
207      */
208 	public boolean isFlat() {
209 		return flat;
210 	}
211 
212     /**
213      * If there is a method or constructor configured as a @Cloner instance, the CloneImplementor 
214      * that can invoke this method will be returned
215      * @return The configured CloneImplementor or null
216      */
217 	public CloneImplementor getCloneImplementor() {
218 		return cloneImplementor;
219 	}
220 
221 	/**
222 	 * Return an array of FieldModel for the class - one entry per Field
223 	 * @return The FieldModels for the class
224 	 */
225 	public FieldModel<C>[] getModelFields() {
226 		return modelFields;
227 	}
228 }