View Javadoc

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