Coverage Report - org.apache.commons.beanutils.MappedPropertyDescriptor
 
Classes in this File Line Coverage Branch Coverage Complexity
MappedPropertyDescriptor
62%
71/114
64%
41/64
5.353
MappedPropertyDescriptor$MappedMethodReference
74%
41/55
65%
17/26
5.353
 
 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.ref.Reference;
 24  
 import java.lang.ref.SoftReference;
 25  
 import java.lang.ref.WeakReference;
 26  
 import java.lang.reflect.Method;
 27  
 import java.lang.reflect.Modifier;
 28  
 
 29  
 
 30  
 /**
 31  
  * A MappedPropertyDescriptor describes one mapped property.
 32  
  * Mapped properties are multivalued properties like indexed properties
 33  
  * but that are accessed with a String key instead of an index.
 34  
  * Such property values are typically stored in a Map collection.
 35  
  * For this class to work properly, a mapped value must have
 36  
  * getter and setter methods of the form
 37  
  * <p><code>get<strong>Property</strong>(String key)<code> and
 38  
  * <p><code>set<strong>Property</strong>(String key, Object value)<code>,
 39  
  * <p>where <code><strong>Property</strong></code> must be replaced
 40  
  * by the name of the property.
 41  
  * @see java.beans.PropertyDescriptor
 42  
  *
 43  
  * @author Rey Francois
 44  
  * @author Gregor Rayman
 45  
  * @version $Revision: 806915 $ $Date: 2009-08-22 20:50:23 -0400 (Sat, 22 Aug 2009) $
 46  
  */
 47  
 
 48  
 
 49  2
 public class MappedPropertyDescriptor extends PropertyDescriptor {
 50  
     // ----------------------------------------------------- Instance Variables
 51  
 
 52  
     /**
 53  
      * The underlying data type of the property we are describing.
 54  
      */
 55  
     private Reference mappedPropertyTypeRef;
 56  
 
 57  
     /**
 58  
      * The reader method for this property (if any).
 59  
      */
 60  
     private MappedMethodReference mappedReadMethodRef;
 61  
 
 62  
     /**
 63  
      * The writer method for this property (if any).
 64  
      */
 65  
     private MappedMethodReference mappedWriteMethodRef;
 66  
 
 67  
     /**
 68  
      * The parameter types array for the reader method signature.
 69  
      */
 70  1
     private static final Class[] STRING_CLASS_PARAMETER = new Class[]{String.class};
 71  
 
 72  
     // ----------------------------------------------------------- Constructors
 73  
 
 74  
     /**
 75  
      * Constructs a MappedPropertyDescriptor for a property that follows
 76  
      * the standard Java convention by having getFoo and setFoo
 77  
      * accessor methods, with the addition of a String parameter (the key).
 78  
      * Thus if the argument name is "fred", it will
 79  
      * assume that the writer method is "setFred" and the reader method
 80  
      * is "getFred".  Note that the property name should start with a lower
 81  
      * case character, which will be capitalized in the method names.
 82  
      *
 83  
      * @param propertyName The programmatic name of the property.
 84  
      * @param beanClass The Class object for the target bean.  For
 85  
      *        example sun.beans.OurButton.class.
 86  
      *
 87  
      * @exception IntrospectionException if an exception occurs during
 88  
      *              introspection.
 89  
      */
 90  
     public MappedPropertyDescriptor(String propertyName, Class beanClass)
 91  
             throws IntrospectionException {
 92  
 
 93  93
         super(propertyName, null, null);
 94  
         
 95  87
         if (propertyName == null || propertyName.length() == 0) {
 96  0
             throw new IntrospectionException("bad property name: " +
 97  
                     propertyName + " on class: " + beanClass.getClass().getName());
 98  
         }
 99  
 
 100  87
         setName(propertyName);
 101  87
         String base = capitalizePropertyName(propertyName);
 102  
         
 103  
         // Look for mapped read method and matching write method
 104  87
         Method mappedReadMethod = null;
 105  87
         Method mappedWriteMethod = null;
 106  
         try {
 107  
             try {
 108  87
                 mappedReadMethod = getMethod(beanClass, "get" + base,
 109  
                         STRING_CLASS_PARAMETER);
 110  60
             } catch (IntrospectionException e) {
 111  60
                 mappedReadMethod = getMethod(beanClass, "is" + base,
 112  
                         STRING_CLASS_PARAMETER);
 113  27
             }
 114  28
             Class[] params = { String.class, mappedReadMethod.getReturnType() };
 115  28
             mappedWriteMethod = getMethod(beanClass, "set" + base, params);
 116  64
         } catch (IntrospectionException e) {
 117  
             /* Swallow IntrospectionException
 118  
              * TODO: Why?
 119  
              */
 120  23
         }
 121  
         
 122  
         // If there's no read method, then look for just a write method 
 123  87
         if (mappedReadMethod == null) {
 124  59
             mappedWriteMethod = getMethod(beanClass, "set" + base, 2);
 125  
         }
 126  
 
 127  36
         if ((mappedReadMethod == null) && (mappedWriteMethod == null)) {
 128  0
             throw new IntrospectionException("Property '" + propertyName +
 129  
                     "' not found on " +
 130  
                     beanClass.getName());
 131  
         }
 132  36
         mappedReadMethodRef  = new MappedMethodReference(mappedReadMethod);
 133  36
         mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
 134  
         
 135  36
         findMappedPropertyType();
 136  36
     }
 137  
 
 138  
 
 139  
     /**
 140  
      * This constructor takes the name of a mapped property, and method
 141  
      * names for reading and writing the property.
 142  
      *
 143  
      * @param propertyName The programmatic name of the property.
 144  
      * @param beanClass The Class object for the target bean.  For
 145  
      *        example sun.beans.OurButton.class.
 146  
      * @param mappedGetterName The name of the method used for
 147  
      *          reading one of the property values.  May be null if the
 148  
      *          property is write-only.
 149  
      * @param mappedSetterName The name of the method used for writing
 150  
      *          one of the property values.  May be null if the property is
 151  
      *          read-only.
 152  
      *
 153  
      * @exception IntrospectionException if an exception occurs during
 154  
      *              introspection.
 155  
      */
 156  
     public MappedPropertyDescriptor(String propertyName, Class beanClass,
 157  
                                     String mappedGetterName, String mappedSetterName)
 158  
             throws IntrospectionException {
 159  
 
 160  0
         super(propertyName, null, null);
 161  
 
 162  0
         if (propertyName == null || propertyName.length() == 0) {
 163  0
             throw new IntrospectionException("bad property name: " +
 164  
                     propertyName);
 165  
         }
 166  0
         setName(propertyName);
 167  
 
 168  
         // search the mapped get and set methods
 169  0
         Method mappedReadMethod = null;
 170  0
         Method mappedWriteMethod = null;
 171  0
         mappedReadMethod =
 172  
             getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
 173  
 
 174  0
         if (mappedReadMethod != null) {
 175  0
             Class[] params = { String.class, mappedReadMethod.getReturnType() };
 176  0
             mappedWriteMethod = 
 177  
                 getMethod(beanClass, mappedSetterName, params);
 178  0
         } else {
 179  0
             mappedWriteMethod =
 180  
                 getMethod(beanClass, mappedSetterName, 2);
 181  
         }
 182  0
         mappedReadMethodRef  = new MappedMethodReference(mappedReadMethod);
 183  0
         mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
 184  
 
 185  0
         findMappedPropertyType();
 186  0
     }
 187  
 
 188  
     /**
 189  
      * This constructor takes the name of a mapped property, and Method
 190  
      * objects for reading and writing the property.
 191  
      *
 192  
      * @param propertyName The programmatic name of the property.
 193  
      * @param mappedGetter The method used for reading one of
 194  
      *          the property values.  May be be null if the property
 195  
      *          is write-only.
 196  
      * @param mappedSetter The method used for writing one the
 197  
      *          property values.  May be null if the property is read-only.
 198  
      *
 199  
      * @exception IntrospectionException if an exception occurs during
 200  
      *              introspection.
 201  
      */
 202  
     public MappedPropertyDescriptor(String propertyName,
 203  
                                     Method mappedGetter, Method mappedSetter)
 204  
             throws IntrospectionException {
 205  
 
 206  0
         super(propertyName, mappedGetter, mappedSetter);
 207  
 
 208  0
         if (propertyName == null || propertyName.length() == 0) {
 209  0
             throw new IntrospectionException("bad property name: " +
 210  
                     propertyName);
 211  
         }
 212  
 
 213  0
         setName(propertyName);
 214  0
         mappedReadMethodRef  = new MappedMethodReference(mappedGetter);
 215  0
         mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
 216  0
         findMappedPropertyType();
 217  0
     }
 218  
 
 219  
     // -------------------------------------------------------- Public Methods
 220  
 
 221  
     /**
 222  
      * Gets the Class object for the property values.
 223  
      *
 224  
      * @return The Java type info for the property values.  Note that
 225  
      * the "Class" object may describe a built-in Java type such as "int".
 226  
      * The result may be "null" if this is a mapped property that
 227  
      * does not support non-keyed access.
 228  
      * <p>
 229  
      * This is the type that will be returned by the mappedReadMethod.
 230  
      */
 231  
     public Class getMappedPropertyType() {
 232  13
         return (Class)mappedPropertyTypeRef.get();
 233  
     }
 234  
 
 235  
     /**
 236  
      * Gets the method that should be used to read one of the property value.
 237  
      *
 238  
      * @return The method that should be used to read the property value.
 239  
      * May return null if the property can't be read.
 240  
      */
 241  
     public Method getMappedReadMethod() {
 242  106
         return mappedReadMethodRef.get();
 243  
     }
 244  
 
 245  
     /**
 246  
      * Sets the method that should be used to read one of the property value.
 247  
      *
 248  
      * @param mappedGetter The mapped getter method.
 249  
      * @throws IntrospectionException If an error occurs finding the
 250  
      * mapped property
 251  
      */
 252  
     public void setMappedReadMethod(Method mappedGetter)
 253  
             throws IntrospectionException {
 254  0
         mappedReadMethodRef = new MappedMethodReference(mappedGetter);
 255  0
         findMappedPropertyType();
 256  0
     }
 257  
 
 258  
     /**
 259  
      * Gets the method that should be used to write one of the property value.
 260  
      *
 261  
      * @return The method that should be used to write one of the property value.
 262  
      * May return null if the property can't be written.
 263  
      */
 264  
     public Method getMappedWriteMethod() {
 265  94
         return mappedWriteMethodRef.get();
 266  
     }
 267  
 
 268  
     /**
 269  
      * Sets the method that should be used to write the property value.
 270  
      *
 271  
      * @param mappedSetter The mapped setter method.
 272  
      * @throws IntrospectionException If an error occurs finding the
 273  
      * mapped property
 274  
      */
 275  
     public void setMappedWriteMethod(Method mappedSetter)
 276  
             throws IntrospectionException {
 277  0
         mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
 278  0
         findMappedPropertyType();
 279  0
     }
 280  
 
 281  
     // ------------------------------------------------------- Private Methods
 282  
 
 283  
     /**
 284  
      * Introspect our bean class to identify the corresponding getter
 285  
      * and setter methods.
 286  
      */
 287  
     private void findMappedPropertyType() throws IntrospectionException {
 288  
         try {
 289  36
             Method mappedReadMethod  = getMappedReadMethod();
 290  36
             Method mappedWriteMethod = getMappedWriteMethod();
 291  36
             Class mappedPropertyType = null;
 292  36
             if (mappedReadMethod != null) {
 293  28
                 if (mappedReadMethod.getParameterTypes().length != 1) {
 294  0
                     throw new IntrospectionException
 295  
                             ("bad mapped read method arg count");
 296  
                 }
 297  28
                 mappedPropertyType = mappedReadMethod.getReturnType();
 298  28
                 if (mappedPropertyType == Void.TYPE) {
 299  0
                     throw new IntrospectionException
 300  
                             ("mapped read method " +
 301  
                             mappedReadMethod.getName() + " returns void");
 302  
                 }
 303  
             }
 304  
 
 305  36
             if (mappedWriteMethod != null) {
 306  31
                 Class[] params = mappedWriteMethod.getParameterTypes();
 307  31
                 if (params.length != 2) {
 308  0
                     throw new IntrospectionException
 309  
                             ("bad mapped write method arg count");
 310  
                 }
 311  31
                 if (mappedPropertyType != null &&
 312  
                         mappedPropertyType != params[1]) {
 313  0
                     throw new IntrospectionException
 314  
                             ("type mismatch between mapped read and write methods");
 315  
                 }
 316  31
                 mappedPropertyType = params[1];
 317  
             }
 318  36
             mappedPropertyTypeRef = new SoftReference(mappedPropertyType);
 319  0
         } catch (IntrospectionException ex) {
 320  0
             throw ex;
 321  36
         }
 322  36
     }
 323  
 
 324  
 
 325  
     /**
 326  
      * Return a capitalized version of the specified property name.
 327  
      *
 328  
      * @param s The property name
 329  
      */
 330  
     private static String capitalizePropertyName(String s) {
 331  87
         if (s.length() == 0) {
 332  0
             return s;
 333  
         }
 334  
 
 335  87
         char[] chars = s.toCharArray();
 336  87
         chars[0] = Character.toUpperCase(chars[0]);
 337  87
         return new String(chars);
 338  
     }
 339  
 
 340  
     /**
 341  
      * Find a method on a class with a specified number of parameters.
 342  
      */
 343  
     private static Method internalGetMethod(Class initial, String methodName,
 344  
                                             int parameterCount) {
 345  
         // For overridden methods we need to find the most derived version.
 346  
         // So we start with the given class and walk up the superclass chain.
 347  212
         for (Class clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
 348  140
             Method[] methods = clazz.getDeclaredMethods();
 349  2009
             for (int i = 0; i < methods.length; i++) {
 350  1877
                 Method method = methods[i];
 351  1877
                 if (method == null) {
 352  0
                     continue;
 353  
                 }
 354  
                 // skip static methods.
 355  1877
                 int mods = method.getModifiers();
 356  1877
                 if (!Modifier.isPublic(mods) ||
 357  
                     Modifier.isStatic(mods)) {
 358  56
                     continue;
 359  
                 }
 360  1567
                 if (method.getName().equals(methodName) &&
 361  
                         method.getParameterTypes().length == parameterCount) {
 362  8
                     return method;
 363  
                 }
 364  
             }
 365  
         }
 366  
 
 367  
         // Now check any inherited interfaces.  This is necessary both when
 368  
         // the argument class is itself an interface, and when the argument
 369  
         // class is an abstract class.
 370  72
         Class[] interfaces = initial.getInterfaces();
 371  93
         for (int i = 0; i < interfaces.length; i++) {
 372  21
             Method method = internalGetMethod(interfaces[i], methodName, parameterCount);
 373  21
             if (method != null) {
 374  0
                 return method;
 375  
             }
 376  
         }
 377  
 
 378  72
         return null;
 379  
     }
 380  
 
 381  
     /**
 382  
      * Find a method on a class with a specified number of parameters.
 383  
      */
 384  
     private static Method getMethod(Class clazz, String methodName, int parameterCount)
 385  
             throws IntrospectionException {
 386  59
         if (methodName == null) {
 387  0
             return null;
 388  
         }
 389  
 
 390  59
         Method method = internalGetMethod(clazz, methodName, parameterCount);
 391  59
         if (method != null) {
 392  8
             return method;
 393  
         }
 394  
 
 395  
         // No Method found
 396  51
         throw new IntrospectionException("No method \"" + methodName +
 397  
                 "\" with " + parameterCount + " parameter(s)");
 398  
     }
 399  
 
 400  
     /**
 401  
      * Find a method on a class with a specified parameter list.
 402  
      */
 403  
     private static Method getMethod(Class clazz, String methodName, Class[] parameterTypes) 
 404  
                                            throws IntrospectionException {
 405  175
         if (methodName == null) {
 406  0
             return null;
 407  
         }
 408  
 
 409  175
         Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
 410  175
         if (method != null) {
 411  51
             return method;
 412  
         }
 413  
 
 414  124
         int parameterCount = (parameterTypes == null) ? 0 : parameterTypes.length;
 415  
 
 416  
         // No Method found
 417  124
         throw new IntrospectionException("No method \"" + methodName +
 418  
                 "\" with " + parameterCount + " parameter(s) of matching types.");
 419  
     }
 420  
 
 421  
     /**
 422  
      * Holds a {@link Method} in a {@link SoftReference} so that it
 423  
      * it doesn't prevent any ClassLoader being garbage collected, but
 424  
      * tries to re-create the method if the method reference has been
 425  
      * released.
 426  
      *
 427  
      * See http://issues.apache.org/jira/browse/BEANUTILS-291
 428  
      */
 429  200
     private static class MappedMethodReference {
 430  
         private String className;
 431  
         private String methodName;
 432  
         private Reference methodRef;
 433  
         private Reference classRef;
 434  
         private Reference writeParamTypeRef0;
 435  
         private Reference writeParamTypeRef1;
 436  
         private String[] writeParamClassNames;
 437  72
         MappedMethodReference(Method m) {
 438  72
             if (m != null) {
 439  59
                 className = m.getDeclaringClass().getName();
 440  59
                 methodName = m.getName();
 441  59
                 methodRef = new SoftReference(m);
 442  59
                 classRef = new WeakReference(m.getDeclaringClass());
 443  59
                 Class[] types = m.getParameterTypes();
 444  59
                 if (types.length == 2) {
 445  31
                     writeParamTypeRef0 = new WeakReference(types[0]);
 446  31
                     writeParamTypeRef1 = new WeakReference(types[1]);
 447  31
                     writeParamClassNames = new String[2];
 448  31
                     writeParamClassNames[0] = types[0].getName();
 449  31
                     writeParamClassNames[1] = types[1].getName();
 450  
                 }
 451  
             }
 452  72
         }
 453  
         private Method get() {
 454  200
             if (methodRef == null) {
 455  28
                 return null;
 456  
             }
 457  172
             Method m = (Method)methodRef.get();
 458  172
             if (m == null) {
 459  5
                 Class clazz = (Class)classRef.get();
 460  5
                 if (clazz == null) {
 461  2
                     clazz = reLoadClass();
 462  2
                     if (clazz != null) {
 463  2
                         classRef = new WeakReference(clazz);
 464  
                     }
 465  
                 }
 466  5
                 if (clazz == null) {
 467  0
                     throw new RuntimeException("Method " + methodName + " for " +
 468  
                             className + " could not be reconstructed - class reference has gone");
 469  
                 }
 470  5
                 Class[] paramTypes = null;
 471  5
                 if (writeParamClassNames != null) {
 472  3
                     paramTypes = new Class[2];
 473  3
                     paramTypes[0] = (Class)writeParamTypeRef0.get();
 474  3
                     if (paramTypes[0] == null) {
 475  0
                         paramTypes[0] = reLoadClass(writeParamClassNames[0]);
 476  0
                         if (paramTypes[0] != null) {
 477  0
                             writeParamTypeRef0 = new WeakReference(paramTypes[0]);
 478  
                         }
 479  
                     }
 480  3
                     paramTypes[1] = (Class)writeParamTypeRef1.get();
 481  3
                     if (paramTypes[1] == null) {
 482  0
                         paramTypes[1] = reLoadClass(writeParamClassNames[1]);
 483  0
                         if (paramTypes[1] != null) {
 484  0
                             writeParamTypeRef1 = new WeakReference(paramTypes[1]);
 485  
                         }
 486  
                     }
 487  
                 } else {
 488  2
                     paramTypes = STRING_CLASS_PARAMETER;
 489  
                 }
 490  
                 try {
 491  5
                     m = clazz.getMethod(methodName, paramTypes);
 492  
                     // Un-comment following line for testing
 493  
                     // System.out.println("Recreated Method " + methodName + " for " + className);
 494  0
                 } catch (NoSuchMethodException e) {
 495  0
                     throw new RuntimeException("Method " + methodName + " for " +
 496  
                             className + " could not be reconstructed - method not found");
 497  5
                 }
 498  5
                 methodRef = new SoftReference(m);
 499  
             }
 500  172
             return m;
 501  
         }
 502  
 
 503  
         /**
 504  
          * Try to re-load the class
 505  
          */
 506  
         private Class reLoadClass() {
 507  2
             return reLoadClass(className);
 508  
         }
 509  
 
 510  
         /**
 511  
          * Try to re-load the class
 512  
          */
 513  
         private Class reLoadClass(String name) {
 514  
 
 515  2
             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 516  
 
 517  
             // Try the context class loader
 518  2
             if (classLoader != null) {
 519  
                 try {
 520  2
                     return classLoader.loadClass(name);
 521  0
                 } catch (ClassNotFoundException e) {
 522  
                     // ignore
 523  
                 }
 524  
             }
 525  
 
 526  
             // Try this class's class loader
 527  0
             classLoader = MappedPropertyDescriptor.class.getClassLoader();
 528  
             try {
 529  0
                 return classLoader.loadClass(name);
 530  0
             } catch (ClassNotFoundException e) {
 531  0
                 return null;
 532  
             }
 533  
         }
 534  
     }
 535  
 }