001package org.jadira.reflection.access.model; 002 003import java.lang.annotation.Annotation; 004import java.lang.reflect.Constructor; 005import java.lang.reflect.Field; 006import java.lang.reflect.Method; 007import java.util.concurrent.ConcurrentHashMap; 008 009import org.jadira.reflection.access.api.ClassAccess; 010import org.jadira.reflection.cloning.annotation.Cloner; 011import org.jadira.reflection.cloning.annotation.Flat; 012import org.jadira.reflection.cloning.annotation.Immutable; 013import org.jadira.reflection.cloning.annotation.NonCloneable; 014import org.jadira.reflection.cloning.api.CloneImplementor; 015import org.jadira.reflection.cloning.implementor.reflection.CopyConstructorImplementor; 016import org.jadira.reflection.cloning.implementor.reflection.ReflectionMethodImplementor; 017import org.jadira.reflection.cloning.mutability.MutabilityDetector; 018import org.jadira.reflection.core.misc.ClassUtils; 019import org.jadira.reflection.core.platform.FeatureDetection; 020import org.mutabilitydetector.checkers.MutabilityAnalysisException; 021 022/** 023 * Provides a base model resulting from introspection of a class 024 */ 025public class ClassModel<C> { 026 027 private static final boolean MUTABILITY_DETECTOR_AVAILABLE = FeatureDetection.hasMutabilityDetector(); 028 029 private static final Class<Annotation> JSR305_IMMUTABLE_ANNOTATION; 030 031 private static final ConcurrentHashMap<String, ClassModel<?>> classModels = new ConcurrentHashMap<String, ClassModel<?>>(16); 032 033 private static final Object MONITOR = new Object(); 034 035 static { 036 Class<Annotation> immutableAnnotation; 037 try { 038 @SuppressWarnings("unchecked") 039 final Class<Annotation> myImmutableAnnotation = (Class<Annotation>) Class.forName("javax.annotation.concurrent.Immutable"); 040 immutableAnnotation = myImmutableAnnotation; 041 } catch (ClassNotFoundException e) { 042 immutableAnnotation = null; 043 } 044 JSR305_IMMUTABLE_ANNOTATION = immutableAnnotation; 045 } 046 047 private final Class<?> modelClass; 048 private final boolean detectedAsImmutable; 049 private final boolean nonCloneable; 050 private final boolean flat; 051 052 private final CloneImplementor cloneImplementor; 053 054 private final ClassAccess<C> classAccess; 055 056 private FieldModel<C>[] modelFields; 057 058 private ClassModel<? super C> superClassModel; 059 060 /** 061 * Returns a class model for the given ClassAccess instance. If a ClassModel 062 * already exists, it will be reused. 063 * @param classAccess The ClassAccess 064 * @param <C> The type of class 065 * @return The Field Model 066 */ 067 @SuppressWarnings("unchecked") 068 public static final <C> ClassModel<C> get(ClassAccess<C> classAccess) { 069 070 Class<?> clazz = classAccess.getType(); 071 072 String classModelKey = (classAccess.getClass().getName() + ":" + clazz.getName()); 073 074 ClassModel<C> classModel = (ClassModel<C>)classModels.get(classModelKey); 075 if (classModel != null) { 076 return classModel; 077 } 078 079 synchronized(MONITOR) { 080 classModel = (ClassModel<C>)classModels.get(classModelKey); 081 if (classModel != null) { 082 return classModel; 083 } else { 084 classModel = new ClassModel<C>(classAccess); 085 classModels.put(classModelKey, classModel); 086 087 return classModel; 088 } 089 } 090 } 091 092 private ClassModel(ClassAccess<C> classAccess) { 093 094 this.classAccess = classAccess; 095 this.modelClass = classAccess.getType(); 096 097 boolean myDetectedAsImmutable = false; 098 try { 099 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}