View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package org.apache.jdo.impl.model.java.reflection;
18  
19  import java.util.Map;
20  import java.util.HashMap;
21  
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Method;
24  
25  import org.apache.jdo.impl.model.java.PredefinedType;
26  import org.apache.jdo.impl.model.java.BaseReflectionJavaType;
27  import org.apache.jdo.impl.model.java.JavaPropertyImpl;
28  import org.apache.jdo.model.ModelFatalException;
29  import org.apache.jdo.model.java.JavaField;
30  import org.apache.jdo.model.java.JavaMethod;
31  import org.apache.jdo.model.java.JavaModel;
32  import org.apache.jdo.model.java.JavaProperty;
33  import org.apache.jdo.model.java.JavaType;
34  import org.apache.jdo.model.jdo.JDOClass;
35  import org.apache.jdo.model.jdo.JDOField;
36  
37  /***
38   * A reflection based JavaType implementation used at runtime.  
39   * The implementation takes <code>java.lang.Class</code> and
40   * <code>java.lang.reflect.Field</code> instances to get Java related
41   * metadata about types and fields. 
42   *
43   * @author Michael Bouschen
44   * @since JDO 1.1
45   * @version JDO 2.0
46   */
47  public class ReflectionJavaType
48      extends BaseReflectionJavaType
49  {
50      /*** The declaring JavaModel instance. */
51      protected final ReflectionJavaModel declaringJavaModel;
52  
53      /*** Flag indicating whether the superclass is checked already. */
54      private boolean superclassUnchecked = true;
55  
56      /*** Flag indicating whether the JDOClass info is retrieved already. */
57      private boolean jdoClassUnchecked = true;
58  
59      /*** The JDO metadata, if this type represents a pc class. */
60      private JDOClass jdoClass;
61  
62      /*** Map of JavaField instances, key is the field name. */
63      protected Map declaredJavaFields = new HashMap();
64  
65      /*** Map of JavaProperty instances, key is the property name. */
66      protected Map declaredJavaProperties = new HashMap();
67  
68      /*** Flag indicating whether thsi JavaTYpe has been introspected. */
69      private boolean introspected = false;
70  
71      /*** Constructor. */
72      public ReflectionJavaType(Class clazz, 
73          ReflectionJavaModel declaringJavaModel)
74      {
75          // Pass null as the superclass to the super call. This allows lazy
76          // evaluation of the superclass (see getSuperclass implementation).
77          super(clazz, null); 
78          this.declaringJavaModel = declaringJavaModel;
79      }
80  
81      /***
82       * Determines if this JavaType object represents an array type.
83       * @return <code>true</code> if this object represents an array type; 
84       * <code>false</code> otherwise.
85       */
86      public boolean isArray()
87      {
88          return clazz.isArray();
89      }
90  
91      /*** 
92       * Returns <code>true</code> if this JavaType represents a persistence
93       * capable class.
94       * <p>
95       * A {@link org.apache.jdo.model.ModelFatalException} indicates a
96       * problem accessing the JDO meta data for this JavaType.
97       * @return <code>true</code> if this JavaType represents a persistence
98       * capable class; <code>false</code> otherwise.
99       * @exception ModelFatalException if there is a problem accessing the
100      * JDO metadata
101      */
102     public boolean isPersistenceCapable()
103         throws ModelFatalException
104     {
105         return (getJDOClass() != null);
106     }
107 
108     /*** 
109      * Returns the JavaType representing the superclass of the entity
110      * represented by this JavaType. If this JavaType represents either the 
111      * Object class, an interface, a primitive type, or <code>void</code>, 
112      * then <code>null</code> is returned. If this object represents an
113      * array class then the JavaType instance representing the Object class
114      * is returned.  
115      * @return the superclass of the class represented by this JavaType.
116      */
117     public synchronized JavaType getSuperclass()
118     {
119         if (superclassUnchecked) {
120             superclassUnchecked = false;
121             superclass = getJavaTypeForClass(clazz.getSuperclass());
122         }
123         return superclass;
124     }
125 
126     /***
127      * Returns the JDOClass instance if this JavaType represents a
128      * persistence capable class. The method returns <code>null</code>, 
129      * if this JavaType does not represent a persistence capable class.
130      * <p>
131      * A {@link org.apache.jdo.model.ModelFatalException} indicates a
132      * problem accessing the JDO meta data for this JavaType.
133      * @return the JDOClass instance if this JavaType represents a
134      * persistence capable class; <code>null</code> otherwise.
135      * @exception ModelFatalException if there is a problem accessing the
136      * JDO metadata
137      */
138     public synchronized JDOClass getJDOClass()
139         throws ModelFatalException
140     {
141         if (jdoClassUnchecked) {
142             jdoClassUnchecked = false;
143             jdoClass = declaringJavaModel.getJDOModel().getJDOClass(getName());
144         }
145         return jdoClass;
146     }
147  
148     /*** 
149      * Returns the JavaType representing the component type of an array. 
150      * If this JavaType does not represent an array type this method
151      * returns <code>null</code>.
152      * @return the JavaType representing the component type of this
153      * JavaType if this class is an array; <code>null</code> otherwise. 
154      */ 
155     public JavaType getArrayComponentType()
156     {
157         JavaType componentType = null;
158         if (isArray()) {
159             Class componentClass = clazz.getComponentType();
160             if (componentClass != null)
161                 componentType = getJavaTypeForClass(componentClass);
162         }
163         return componentType;
164     }
165 
166     /***
167      * Returns a JavaField instance that reflects the field with the
168      * specified name of the class or interface represented by this
169      * JavaType instance. The method returns <code>null</code>, if the
170      * class or interface (or one of its superclasses) does not have a
171      * field with that name.
172      * @param fieldName the name of the field 
173      * @return the JavaField instance for the specified field in this class
174      * or <code>null</code> if there is no such field.
175      */
176     public JavaField getJavaField(String fieldName) 
177     { 
178         JavaField javaField = getDeclaredJavaField(fieldName);
179         if (javaField == null) {
180             // check superclass
181             JavaType superclass = getSuperclass();
182             if ((superclass != null) &&
183                 (superclass != PredefinedType.objectType)) {
184                 javaField = superclass.getJavaField(fieldName);
185             }
186         }
187         return javaField;
188     }
189 
190     /***
191      * Returns an array of JavaField instances representing the declared
192      * fields of the class represented by this JavaType instance. Note, this
193      * method does not return JavaField instances representing inherited
194      * fields. 
195      * @return an array of declared JavaField instances. 
196      */
197     public JavaField[] getDeclaredJavaFields()
198     {
199         introspectClass();
200         return (JavaField[]) declaredJavaFields.values().toArray(
201             new JavaField[0]);
202     }
203     
204      /***
205      * Returns a JavaProperty instance that reflects the property with the
206      * specified name of the class or interface represented by this
207      * JavaType instance. The method returns <code>null</code>, if the
208      * class or interface (or one of its superclasses) does not have a
209      * field with that name.
210      * @param name the name of the property 
211      * @return the JavaProperty instance for the specified property in this
212      * class or <code>null</code> if there is no such property.
213      */
214     public JavaProperty getJavaProperty(String name)
215     {
216         JavaProperty javaProperty = getDeclaredJavaProperty(name);
217         if (javaProperty == null) {
218             // check superclass
219             JavaType superclass = getSuperclass();
220             if ((superclass != null) &&
221                 (superclass != PredefinedType.objectType)) {
222                 javaProperty = superclass.getJavaProperty(name);
223             }
224         }
225         return javaProperty;
226     }
227 
228     /***
229      * Returns an array of JavaProperty instances representing the declared
230      * properties of the class represented by this JavaType instance. Note,
231      * this method does not return JavaField instances representing inherited
232      * properties. 
233      * @return an array of declared JavaField instances. 
234      */
235     public JavaProperty[] getDeclaredJavaProperties()
236     {
237         introspectClass();
238         return (JavaProperty[]) declaredJavaProperties.values().toArray(
239             new JavaProperty[0]);
240     }
241 
242     // ===== Methods not specified in JavaType =====
243 
244     /***
245      * Returns a JavaField instance that reflects the declared field with
246      * the specified name of the class or interface represented by this
247      * JavaType instance. The method returns <code>null</code>, if the 
248      * class or interface does not declared a field with that name. It does
249      * not check whether one of its superclasses declares such a field.
250      * @param fieldName the name of the field 
251      * @return the JavaField instance for the specified field in this class
252      */
253     public synchronized JavaField getDeclaredJavaField(String fieldName)
254     {
255         JavaField javaField = (JavaField)declaredJavaFields.get(fieldName);
256         if (javaField == null) {
257             JDOClass jdoClass = getJDOClass();
258             if (jdoClass != null) {
259                 // pc class => look for JDOField first
260                 if (jdoClass.getDeclaredField(fieldName) != null) {
261                     // Use JDO metadata and create a JavaField skeleton to
262                     // avoid unnecessary reflection access.
263                     javaField = newJavaFieldInstance(fieldName, null);
264                     declaredJavaFields.put(fieldName, javaField);
265                 }
266             }
267             
268             // if no field info check reflection
269             if (javaField == null) {
270                 Field field = ReflectionJavaField.getDeclaredFieldPrivileged(
271                     clazz, fieldName);
272                 if (field != null) {
273                     javaField = newJavaFieldInstance(field);
274                     declaredJavaFields.put(fieldName, javaField);
275                 }
276             }
277         }
278         return javaField;   
279     }
280 
281     /***
282      * Returns a JavaProperty instance that reflects the declared property
283      * with the specified name of the class or interface represented by this
284      * JavaType instance. The method returns <code>null</code>, if the 
285      * class or interface does not declared a property with that name. It does
286      * not check whether one of its superclasses declares such a property.
287      * @param name the name of the property 
288      * @return the JavaField instance for the specified property in this class
289      */
290     public JavaProperty getDeclaredJavaProperty(String name) 
291     {
292         introspectClass();
293         return (JavaProperty)declaredJavaProperties.get(name);
294     }
295 
296     /*** 
297      * Returns a JavaType instance for the specified Class object. 
298      * This method provides a hook such that ReflectionJavaType subclasses can
299      * implement their own mapping of Class objects to JavaType instances. 
300      */
301     public JavaType getJavaTypeForClass(Class clazz)
302     {
303         return declaringJavaModel.getDeclaringJavaModelFactory().getJavaType(clazz);
304     }
305 
306     /*** 
307      * Creates a new JavaProperty instance and adds it to the list of
308      * declared properties of this class.
309      * @param name the name of the property
310      * @param getter the getter method
311      * @param setter the setter method
312      * @param type the ytpe of the property
313      * @return a new JavaProperty declared by this class
314      */
315     public synchronized JavaProperty createJavaProperty(
316         String name, JavaMethod getter, JavaMethod setter, JavaType type)
317         throws ModelFatalException
318     {
319         JavaProperty javaProperty = 
320             newJavaPropertyInstance(name, getter, setter, type);
321         declaredJavaProperties.put(name, javaProperty);
322         return javaProperty;
323     }
324 
325     /***
326      * Creates a new JavaMethod instance.
327      * @param method the java.lang.reflect.Method instance
328      * @return a new JavaMethod declared by this class
329      */
330     public JavaMethod createJavaMethod(Method method)
331     {
332         return newJavaMethodInstance(method);
333     }
334 
335     /***
336      * Creates a new instance of the JavaField implementation class.
337      * <p>
338      * This implementation returns a <code>ReflectionJavaField</code>
339      * instance.
340      * @return a new JavaField instance.
341      */
342     protected JavaField newJavaFieldInstance(String fieldName, JavaType type) 
343     {
344         return new ReflectionJavaField(fieldName, type, this);
345     }
346     
347     /***
348      * Creates a new instance of the JavaField implementation class.
349      * <p>
350      * This implementation returns a <code>ReflectionJavaField</code>
351      * instance.
352      * @return a new JavaField instance.
353      */
354     protected JavaField newJavaFieldInstance(Field field) 
355     {
356         return new ReflectionJavaField(field, this);
357     }
358     
359     /***
360      * Creates a new instance of the JavaProperty implementation class.
361      * <p>
362      * This implementation returns a <code>JavaPropertyImpl</code>
363      * instance.
364      * @return a new JavaProperty instance.
365      */
366     protected JavaProperty newJavaPropertyInstance(String name, 
367             JavaMethod getter, JavaMethod setter, JavaType type) 
368         throws ModelFatalException
369     {
370         return new JavaPropertyImpl(name, getter, setter, type, this);
371     }
372 
373     /***
374      * Creates a new instance of the JavaMethod implementation class.
375      * <p>
376      * This implementation returns a <code>ReflectionJavaMethod</code>
377      * instance.
378      * @return a new JavaMethod instance.
379      */
380     protected JavaMethod newJavaMethodInstance(Method method) 
381     {
382         return new ReflectionJavaMethod(method, this);
383     }
384 
385     /*** 
386      * Helper method to introspect the class and set the declared fields and
387      * properties. 
388      */
389     protected synchronized void introspectClass() 
390     {
391         if (introspected)
392             // has been introspected before => return;
393             return;
394         
395         introspected = true;
396         
397         new ReflectionJavaTypeIntrospector().addDeclaredJavaProperties(this);
398 
399         // now get all the declared fields
400         Field[] fields = ReflectionJavaField.getDeclaredFieldsPrivileged(clazz);
401         for (int i = 0; i < fields.length; i++) {
402             Field field = fields[i];
403             String fieldName = field.getName();
404             if (declaredJavaFields.get(fieldName) == null) {
405                 JavaField javaField = newJavaFieldInstance(field);
406                 declaredJavaFields.put(fieldName, javaField);
407             }
408         }
409     }
410 }