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}