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 }