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
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
62
63
64
65
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
123 } catch (SecurityException e) {
124
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
163
164
165 public Class<?> getModelClass() {
166 return modelClass;
167 }
168
169
170
171
172
173 public ClassAccess<C> getClassAccess() {
174 return classAccess;
175 }
176
177
178
179
180
181 public ClassModel<? super C> getSuperClassModel() {
182 return superClassModel;
183 }
184
185
186
187
188
189 public boolean isDetectedAsImmutable() {
190 return detectedAsImmutable;
191 }
192
193
194
195
196
197
198 public boolean isNonCloneable() {
199 return nonCloneable;
200 }
201
202
203
204
205
206
207
208 public boolean isFlat() {
209 return flat;
210 }
211
212
213
214
215
216
217 public CloneImplementor getCloneImplementor() {
218 return cloneImplementor;
219 }
220
221
222
223
224
225 public FieldModel<C>[] getModelFields() {
226 return modelFields;
227 }
228 }