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.commons.beanutils;
19  
20  
21  import java.beans.IntrospectionException;
22  import java.beans.PropertyDescriptor;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Modifier;
25  
26  
27  /***
28   * A MappedPropertyDescriptor describes one mapped property.
29   * Mapped properties are multivalued properties like indexed properties
30   * but that are accessed with a String key instead of an index.
31   * Such property values are typically stored in a Map collection.
32   * For this class to work properly, a mapped value must have
33   * getter and setter methods of the form
34   * <p><code>get<strong>Property</strong>(String key)<code> and
35   * <p><code>set<strong>Property</strong>(String key, Object value)<code>,
36   * <p>where <code><strong>Property</strong></code> must be replaced
37   * by the name of the property.
38   * @see java.beans.PropertyDescriptor
39   *
40   * @author Rey Francois
41   * @author Gregor Rayman
42   * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
43   */
44  
45  
46  public class MappedPropertyDescriptor extends PropertyDescriptor {
47      // ----------------------------------------------------- Instance Variables
48  
49      /***
50       * The underlying data type of the property we are describing.
51       */
52      private Class mappedPropertyType;
53  
54      /***
55       * The reader method for this property (if any).
56       */
57      private Method mappedReadMethod;
58  
59      /***
60       * The writer method for this property (if any).
61       */
62      private Method mappedWriteMethod;
63  
64      /***
65       * The parameter types array for the reader method signature.
66       */
67      private static final Class[] STRING_CLASS_PARAMETER = new Class[]{String.class};
68  
69      // ----------------------------------------------------------- Constructors
70  
71      /***
72       * Constructs a MappedPropertyDescriptor for a property that follows
73       * the standard Java convention by having getFoo and setFoo
74       * accessor methods, with the addition of a String parameter (the key).
75       * Thus if the argument name is "fred", it will
76       * assume that the writer method is "setFred" and the reader method
77       * is "getFred".  Note that the property name should start with a lower
78       * case character, which will be capitalized in the method names.
79       *
80       * @param propertyName The programmatic name of the property.
81       * @param beanClass The Class object for the target bean.  For
82       *        example sun.beans.OurButton.class.
83       *
84       * @exception IntrospectionException if an exception occurs during
85       *              introspection.
86       */
87      public MappedPropertyDescriptor(String propertyName, Class beanClass)
88              throws IntrospectionException {
89  
90          super(propertyName, null, null);
91          
92          if (propertyName == null || propertyName.length() == 0) {
93              throw new IntrospectionException("bad property name: " +
94                      propertyName + " on class: " + beanClass.getClass().getName());
95          }
96  
97          setName(propertyName);
98          String base = capitalizePropertyName(propertyName);
99          
100         // Look for mapped read method and matching write method
101         try {
102             try {
103                 mappedReadMethod = getMethod(beanClass, "get" + base,
104                         STRING_CLASS_PARAMETER);
105             } catch (IntrospectionException e) {
106                 mappedReadMethod = getMethod(beanClass, "is" + base,
107                         STRING_CLASS_PARAMETER);
108             }
109             Class[] params = { String.class, mappedReadMethod.getReturnType() };
110             mappedWriteMethod = getMethod(beanClass, "set" + base, params);
111         } catch (IntrospectionException e) {
112             /* Swallow IntrospectionException
113              * TODO: Why?
114              */
115         }
116         
117         // If there's no read method, then look for just a write method 
118         if (mappedReadMethod == null) {
119             mappedWriteMethod = getMethod(beanClass, "set" + base, 2);
120         }
121 
122         if ((mappedReadMethod == null) && (mappedWriteMethod == null)) {
123             throw new IntrospectionException("Property '" + propertyName +
124                     "' not found on " +
125                     beanClass.getName());
126         }
127         
128         findMappedPropertyType();
129     }
130 
131 
132     /***
133      * This constructor takes the name of a mapped property, and method
134      * names for reading and writing the property.
135      *
136      * @param propertyName The programmatic name of the property.
137      * @param beanClass The Class object for the target bean.  For
138      *        example sun.beans.OurButton.class.
139      * @param mappedGetterName The name of the method used for
140      *          reading one of the property values.  May be null if the
141      *          property is write-only.
142      * @param mappedSetterName The name of the method used for writing
143      *          one of the property values.  May be null if the property is
144      *          read-only.
145      *
146      * @exception IntrospectionException if an exception occurs during
147      *              introspection.
148      */
149     public MappedPropertyDescriptor(String propertyName, Class beanClass,
150                                     String mappedGetterName, String mappedSetterName)
151             throws IntrospectionException {
152 
153         super(propertyName, null, null);
154 
155         if (propertyName == null || propertyName.length() == 0) {
156             throw new IntrospectionException("bad property name: " +
157                     propertyName);
158         }
159         setName(propertyName);
160 
161         // search the mapped get and set methods
162         mappedReadMethod =
163             getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
164 
165         if (mappedReadMethod != null) {
166             Class[] params = { String.class, mappedReadMethod.getReturnType() };
167             mappedWriteMethod = 
168                 getMethod(beanClass, mappedSetterName, params);
169         } else {
170             mappedWriteMethod =
171                 getMethod(beanClass, mappedSetterName, 2);
172         }
173 
174         findMappedPropertyType();
175     }
176 
177     /***
178      * This constructor takes the name of a mapped property, and Method
179      * objects for reading and writing the property.
180      *
181      * @param propertyName The programmatic name of the property.
182      * @param mappedGetter The method used for reading one of
183      *          the property values.  May be be null if the property
184      *          is write-only.
185      * @param mappedSetter The method used for writing one the
186      *          property values.  May be null if the property is read-only.
187      *
188      * @exception IntrospectionException if an exception occurs during
189      *              introspection.
190      */
191     public MappedPropertyDescriptor(String propertyName,
192                                     Method mappedGetter, Method mappedSetter)
193             throws IntrospectionException {
194 
195         super(propertyName, mappedGetter, mappedSetter);
196 
197         if (propertyName == null || propertyName.length() == 0) {
198             throw new IntrospectionException("bad property name: " +
199                     propertyName);
200         }
201 
202         setName(propertyName);
203         mappedReadMethod = mappedGetter;
204         mappedWriteMethod = mappedSetter;
205         findMappedPropertyType();
206     }
207 
208     // -------------------------------------------------------- Public Methods
209 
210     /***
211      * Gets the Class object for the property values.
212      *
213      * @return The Java type info for the property values.  Note that
214      * the "Class" object may describe a built-in Java type such as "int".
215      * The result may be "null" if this is a mapped property that
216      * does not support non-keyed access.
217      * <p>
218      * This is the type that will be returned by the mappedReadMethod.
219      */
220     public Class getMappedPropertyType() {
221         return mappedPropertyType;
222     }
223 
224     /***
225      * Gets the method that should be used to read one of the property value.
226      *
227      * @return The method that should be used to read the property value.
228      * May return null if the property can't be read.
229      */
230     public Method getMappedReadMethod() {
231         return mappedReadMethod;
232     }
233 
234     /***
235      * Sets the method that should be used to read one of the property value.
236      *
237      * @param mappedGetter The mapped getter method.
238      * @throws IntrospectionException If an error occurs finding the
239      * mapped property
240      */
241     public void setMappedReadMethod(Method mappedGetter)
242             throws IntrospectionException {
243         mappedReadMethod = mappedGetter;
244         findMappedPropertyType();
245     }
246 
247     /***
248      * Gets the method that should be used to write one of the property value.
249      *
250      * @return The method that should be used to write one of the property value.
251      * May return null if the property can't be written.
252      */
253     public Method getMappedWriteMethod() {
254         return mappedWriteMethod;
255     }
256 
257     /***
258      * Sets the method that should be used to write the property value.
259      *
260      * @param mappedSetter The mapped setter method.
261      * @throws IntrospectionException If an error occurs finding the
262      * mapped property
263      */
264     public void setMappedWriteMethod(Method mappedSetter)
265             throws IntrospectionException {
266         mappedWriteMethod = mappedSetter;
267         findMappedPropertyType();
268     }
269 
270     // ------------------------------------------------------- Private Methods
271 
272     /***
273      * Introspect our bean class to identify the corresponding getter
274      * and setter methods.
275      */
276     private void findMappedPropertyType() throws IntrospectionException {
277         try {
278             mappedPropertyType = null;
279             if (mappedReadMethod != null) {
280                 if (mappedReadMethod.getParameterTypes().length != 1) {
281                     throw new IntrospectionException
282                             ("bad mapped read method arg count");
283                 }
284                 mappedPropertyType = mappedReadMethod.getReturnType();
285                 if (mappedPropertyType == Void.TYPE) {
286                     throw new IntrospectionException
287                             ("mapped read method " +
288                             mappedReadMethod.getName() + " returns void");
289                 }
290             }
291 
292             if (mappedWriteMethod != null) {
293                 Class[] params = mappedWriteMethod.getParameterTypes();
294                 if (params.length != 2) {
295                     throw new IntrospectionException
296                             ("bad mapped write method arg count");
297                 }
298                 if (mappedPropertyType != null &&
299                         mappedPropertyType != params[1]) {
300                     throw new IntrospectionException
301                             ("type mismatch between mapped read and write methods");
302                 }
303                 mappedPropertyType = params[1];
304             }
305         } catch (IntrospectionException ex) {
306             throw ex;
307         }
308     }
309 
310 
311     /***
312      * Return a capitalized version of the specified property name.
313      *
314      * @param s The property name
315      */
316     private static String capitalizePropertyName(String s) {
317         if (s.length() == 0) {
318             return s;
319         }
320 
321         char[] chars = s.toCharArray();
322         chars[0] = Character.toUpperCase(chars[0]);
323         return new String(chars);
324     }
325 
326     /***
327      * Find a method on a class with a specified number of parameters.
328      */
329     private static Method internalGetMethod(Class initial, String methodName,
330                                             int parameterCount) {
331         // For overridden methods we need to find the most derived version.
332         // So we start with the given class and walk up the superclass chain.
333         for (Class clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
334             Method[] methods = clazz.getDeclaredMethods();
335             for (int i = 0; i < methods.length; i++) {
336                 Method method = methods[i];
337                 if (method == null) {
338                     continue;
339                 }
340                 // skip static methods.
341                 int mods = method.getModifiers();
342                 if (!Modifier.isPublic(mods) ||
343                     Modifier.isStatic(mods)) {
344                     continue;
345                 }
346                 if (method.getName().equals(methodName) &&
347                         method.getParameterTypes().length == parameterCount) {
348                     return method;
349                 }
350             }
351         }
352 
353         // Now check any inherited interfaces.  This is necessary both when
354         // the argument class is itself an interface, and when the argument
355         // class is an abstract class.
356         Class[] interfaces = initial.getInterfaces();
357         for (int i = 0; i < interfaces.length; i++) {
358             Method method = internalGetMethod(interfaces[i], methodName, parameterCount);
359             if (method != null) {
360                 return method;
361             }
362         }
363 
364         return null;
365     }
366 
367     /***
368      * Find a method on a class with a specified number of parameters.
369      */
370     private static Method getMethod(Class clazz, String methodName, int parameterCount)
371             throws IntrospectionException {
372         if (methodName == null) {
373             return null;
374         }
375 
376         Method method = internalGetMethod(clazz, methodName, parameterCount);
377         if (method != null) {
378             return method;
379         }
380 
381         // No Method found
382         throw new IntrospectionException("No method \"" + methodName +
383                 "\" with " + parameterCount + " parameter(s)");
384     }
385 
386     /***
387      * Find a method on a class with a specified parameter list.
388      */
389     private static Method getMethod(Class clazz, String methodName, Class[] parameterTypes) 
390                                            throws IntrospectionException {
391         if (methodName == null) {
392             return null;
393         }
394 
395         Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
396         if (method != null) {
397             return method;
398         }
399 
400         int parameterCount = (parameterTypes == null) ? 0 : parameterTypes.length;
401 
402         // No Method found
403         throw new IntrospectionException("No method \"" + methodName +
404                 "\" with " + parameterCount + " parameter(s) of matching types.");
405     }
406 
407 }