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