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.beans.BeanInfo;
20  import java.beans.Introspector;
21  import java.beans.IntrospectionException;
22  import java.beans.PropertyDescriptor;
23  
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Modifier;
26  
27  import java.security.AccessController;
28  import java.security.PrivilegedAction;
29  
30  import java.util.ArrayList;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.List;
34  
35  import org.apache.jdo.model.ModelFatalException;
36  import org.apache.jdo.model.java.JavaMethod;
37  import org.apache.jdo.model.java.JavaType;
38  import org.apache.jdo.util.I18NHelper;
39  
40  /*** 
41   * Helper class to introspect a ReflectionJavaType representing a class to
42   * find its properties.
43   *
44   * @author Michael Bouschen
45   * @since JDO 2.0
46   */
47  public class ReflectionJavaTypeIntrospector
48  {
49      /*** I18N support */
50      private final static I18NHelper msg = I18NHelper.getInstance(
51          "org.apache.jdo.impl.model.java.Bundle"); //NOI18N
52      
53      /*** 
54       * Adds declared properties to the specified ReflectionJavaType instance. 
55       * @param beanClass the class to be introspected
56       */
57      public void addDeclaredJavaProperties(ReflectionJavaType beanClass) 
58      {
59          Class clazz = beanClass.getJavaClass();
60          PropertyDescriptor[] descrs = 
61              getPublicAndProtectedPropertyDescriptors(clazz);
62          if (descrs != null) {
63              for (int i = 0; i < descrs.length; i++) {
64                  PropertyDescriptor descr = descrs[i];
65                  if (descr == null) continue;
66                  String name = descr.getName();
67                  JavaType type = 
68                      beanClass.getJavaTypeForClass(descr.getPropertyType());
69                  Method getter = descr.getReadMethod();
70                  JavaMethod javaGetter = (getter == null) ? null : 
71                      beanClass.createJavaMethod(getter);
72                  Method setter = descr.getWriteMethod();
73                  JavaMethod javaSetter = (setter == null) ? null : 
74                      beanClass.createJavaMethod(setter);
75                  beanClass.createJavaProperty(name, javaGetter, 
76                                               javaSetter, type);
77              }
78          }
79      }
80  
81      // ===== Implementation using java.beans.Introspector =====
82  
83      /*** 
84       * Returns an array of PropertyDescriptor instances representing the
85       * declared public properties of the specified class.
86       * @param clazz the class to be introspected
87       * @return array of PropertyDescriptor instances for declared public
88       * properties.
89       */
90      private PropertyDescriptor[] getPublicPropertyDescriptors(Class clazz) {
91          try {
92              BeanInfo beanInfo = Introspector.getBeanInfo(
93                  clazz, clazz.getSuperclass());
94              return beanInfo.getPropertyDescriptors();
95          }
96          catch (IntrospectionException ex) {
97              throw new ModelFatalException(msg.msg(
98                  "ERR_CannotIntrospectClass", clazz.getName()), ex); //NOI18N
99          }
100     }
101 
102     // ===== Implementation using hand-written Introspector =====
103 
104     /***
105      * Returns an array of PropertyDescriptor instances representing the
106      * declared public and protected properties of the specified class.
107      * @param clazz the class to be introspected
108      * @return array of PropertyDescriptor instances for declared public and
109      * protected properties.
110      */
111     private PropertyDescriptor[] getPublicAndProtectedPropertyDescriptors(
112         Class clazz) {
113         return new PropertyStore(clazz).getPropertyDescriptors();
114     }
115 
116     /***
117      * Helper class to introspect a class in order to find properties.
118      * The class provides a public method {@link #getPropertyDescriptors()}
119      * returning an array of PropertyDescriptors. Each PropertyDescriptor 
120      * represents a public or protected property of the class specified as
121      * constructor argument. This code is inspired by the implementation
122      * of java.beans.Introspector class. 
123      * <p>
124      * Class PropertyStore uses the following algorithm to identify the
125      * properties:
126      * <ul>
127      * <li>Iterate the declared non-static methods that are public or
128      * protected.</li>
129      * <li>A no-arg method returning a value and having a name staring with
130      * "get" is a potential getter method of a property.</li>
131      * <li>A no-arg method returning a boolean value and a name starting with
132      * "is" is a potential getter method of a property.</li>
133      * <li>A void method with a single argument and having a name starting
134      * with "set" is a potential setter method of a property.</li>
135      * <li>If there exsists an "is" and a "get" method, the "is" method is
136      * used as the getter method. </li>
137      * <li>If there is a getter method and multiple setter methods, it chooses
138      * the setter where the argument has exactly the same type as the getter
139      * return type.</li>
140      * <li>If there no such matching getter method, none of the setter methods
141      * correspond to a property.</li>
142      * </ul>
143      */
144     static class PropertyStore extends HashMap {
145 
146         private static final String GET_PREFIX = "get";    //NOI18N
147         private static final int    GET_PREFIX_LENGTH = 3;
148         private static final String SET_PREFIX = "set";    //NOI18N
149         private static final int    SET_PREFIX_LENGTH = 3;
150         private static final String IS_PREFIX = "is";      //NOI18N
151         private static final int    IS_PREFIX_LENGTH = 2;
152 
153         /*** The declared method instances for the specified class. */
154         private final Method[] declaredMethods;
155 
156         /*** Constructor. */
157         public PropertyStore(final Class clazz) {
158             this.declaredMethods = (Method[]) AccessController.doPrivileged(
159                 new PrivilegedAction() {
160                     public Object run() {
161                         return clazz.getDeclaredMethods();
162                     }});
163         }
164 
165         /***
166          * Returns an array of PropertyDescriptors. Each PropertyDescriptor 
167          * represents a public or protected property of the class specified as
168          * constructor argument.
169          * @return array of public and protected properties
170          */
171         public PropertyDescriptor[] getPropertyDescriptors() {
172             // iterate all declared methods
173             for (int i = 0; i < declaredMethods.length; i++) {
174                 Method method = declaredMethods[i];
175                 int mods = method.getModifiers();
176                 
177                 // we are only interested in non-static methods that are
178                 // public or protected.
179                 if (Modifier.isStatic(mods) || 
180                     (!Modifier.isPublic(mods) && !Modifier.isProtected(mods))) {
181                     continue;
182                 }
183 
184                 String name = method.getName();
185                 Class paramTypes[] = method.getParameterTypes();
186                 Class resultType = method.getReturnType();
187                 
188                 switch (paramTypes.length) {
189                 case 0:
190                     // no args => possible getter
191                     if (name.startsWith(GET_PREFIX) && 
192                         resultType != void.class) {
193                         String propName = Introspector.decapitalize(
194                             name.substring(GET_PREFIX_LENGTH));
195                         addGetter(propName, method);
196                     }
197                     else if (name.startsWith(IS_PREFIX) && 
198                              resultType == boolean.class) {
199                         String propName = Introspector.decapitalize(
200                             name.substring(IS_PREFIX_LENGTH));
201                         addGetter(propName, method);
202                     }
203                     break;
204                 case 1:
205                     // one arg => possible setter
206                     if (name.startsWith(SET_PREFIX) && 
207                         resultType == void.class) {
208                         String propName = Introspector.decapitalize(
209                             name.substring(GET_PREFIX_LENGTH));
210                         addSetter(propName, method);
211                     }
212                     break;
213                 }
214             }
215             
216             // Now merge getters and setters
217             List properties = processProperties();
218             return (PropertyDescriptor[]) properties.toArray(
219                 new PropertyDescriptor[properties.size()]);
220         }
221         
222         /***
223          * Adds a getter method to the methods list for the property with the
224          * specified name.
225          * @param propName the name of the property.
226          * @param method the getter method.
227          */
228         private void addGetter(String propName, Method method) {
229             try {
230                 addPropertyDescriptor(
231                     propName, new PropertyDescriptor(propName, method, null));
232             }
233             catch (IntrospectionException ex) {
234                 throw new ModelFatalException(
235                     msg.msg("ERR_CannotCreatePropertyDescriptor", //NOI18N
236                             propName, method.getName()), ex); 
237             }
238         }
239     
240         /***
241          * Adds a setter method to the methods list for the property with the
242          * specified name.
243          * @param propName the name of the property.
244          * @param method the setter method.
245          */
246         private void addSetter(String propName, Method method) {
247             try {
248                 addPropertyDescriptor(
249                     propName, new PropertyDescriptor(propName, null, method));
250             }
251             catch (IntrospectionException ex) {
252                 throw new ModelFatalException(
253                     msg.msg("ERR_CannotCreatePropertyDescriptor", //NOI18N 
254                             propName, method.getName()), ex);
255             }
256         }
257 
258         /***
259          * Adds a the specified (incomplete) PropertyDescriptor to the list of
260          * PropertyDescriptor candidates managed by this PropertyStore. The
261          * method initializes the list of PropertyDescriptors, in case it is
262          * the first PropertyDescriptor for the property with the specified
263          * name.
264          * @param propName the name of the property.
265          * @param pd new PropertyDescriptor.
266          */
267         private synchronized void addPropertyDescriptor(
268             String propName, PropertyDescriptor pd) {
269             if (pd == null) {
270                 // nothing to be added
271                 return;
272             }
273             
274             List list = (List) get(propName);
275             if (list == null) {
276                 list = new ArrayList();
277                 put(propName, list);
278             }
279             list.add(pd);
280         }
281         
282         /***
283          * The method returns a list of PropertyDescriptors for the properties
284          * managed by this PropertyStore. It iterates the all properties
285          * and analyzes the candidate PropertyDescriptors (by calling method 
286          * {@link #processProperty(List)}.
287          * @return list of PropertyDescriptors
288          */
289         private synchronized List processProperties() {
290             List result = new ArrayList();
291             for (Iterator i = values().iterator(); i.hasNext();) {
292                 PropertyDescriptor pd = processProperty((List) i.next());
293                 if (pd != null) {
294                     result.add(pd);
295                 }
296             }
297             return result;
298         }
299         
300         /*** 
301          * The method analyzes the specified list of candidate
302          * PropertyDescriptors and returns a single PropertyDescriptor
303          * describing the property. It iterates the candidate list in order to
304          * find a getter PropertyDescriptor. If there is such a
305          * PropertyDescriptor it looks for a corresponding setter
306          * PropertyDescriptor and updates the getter PropertyDescriptor with
307          * the write method of the setter. It then returns the getter
308          * PropertyDescriptor. If there is no getter PropertyDescriptor and a
309          * single setter PropertyDescriptor it returns the setter
310          * PropertyDescriptor. Otherwise it returns <code>null</code> which
311          * means the list of candidate PropertyDescriptors does not qualify
312          * for a valid property.
313          * @param candidates the list of candidate PropertyDescriptors
314          * @return a PropertyDescriptor describing a property or
315          * <code>null</code> if the candidate PropertyDescriptors do not
316          * qualify for a valid property.
317          */
318         private PropertyDescriptor processProperty(List candidates) {
319             if (candidates == null)
320                 return null;
321             
322             PropertyDescriptor getter = null;
323             PropertyDescriptor setter = null;
324             
325             // First iteration: check getter methods 
326             for (Iterator i = candidates.iterator(); i.hasNext();) {
327                 PropertyDescriptor pd = (PropertyDescriptor) i.next();
328                 if (pd.getReadMethod() != null) {
329                     if (getter != null) {
330                         // Found getter, but do not overwrite "is" getter
331                         // stored before
332                         String name = getter.getReadMethod().getName();
333                         if (!name.startsWith(IS_PREFIX)) {
334                             getter = pd;
335                         }
336                     }
337                     else {
338                         // Store getter
339                         getter = pd;
340                     }
341                 }
342             }
343             
344             // Second iteration: check setter methods. This cannot be combined
345             // with the first iteration, because I need the property type of
346             // the getter to find the corresponding setter.
347             for (Iterator i = candidates.iterator(); i.hasNext();) {
348                 PropertyDescriptor pd = (PropertyDescriptor) i.next();
349                 if (pd.getWriteMethod() != null) {
350                     if (getter != null) {
351                         if (pd.getPropertyType() == getter.getPropertyType()) {
352                             // Found setter that corresponds to getter => 
353                             // store setter and stop iterating the candidates
354                             setter = pd;
355                             break;
356                         }
357                     }
358                     else if (setter != null) {
359                         // Found multiple setters w/o getter =>
360                         // no property, remove stored setter
361                         setter = null;
362                         break;
363                     }
364                     else {
365                         // Found setter => store it
366                         setter = pd;
367                     }
368                 }
369             }
370             
371             // check stored getter and setter
372             if (getter != null) {
373                 if (setter != null) {
374                     // getter and setter => merge setter into getter and
375                     // return getter
376                     try {
377                         getter.setWriteMethod(setter.getWriteMethod());
378                     }
379                     catch (IntrospectionException ex) {
380                         throw new ModelFatalException(
381                             msg.msg("ERR_CannotSetWriteMethod", //NOI18N
382                                     getter.getName()), ex); 
383                     }            
384                 }
385                 return getter;
386             }
387             return setter;
388         }
389     }
390     
391 }
392