DefaultBeanIntrospector.java

  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. package org.apache.commons.beanutils2;

  18. import java.beans.BeanInfo;
  19. import java.beans.IndexedPropertyDescriptor;
  20. import java.beans.IntrospectionException;
  21. import java.beans.Introspector;
  22. import java.beans.PropertyDescriptor;
  23. import java.lang.reflect.Method;
  24. import java.util.List;

  25. import org.apache.commons.logging.Log;
  26. import org.apache.commons.logging.LogFactory;

  27. /**
  28.  * <p>
  29.  * The default {@link BeanIntrospector} implementation.
  30.  * </p>
  31.  * <p>
  32.  * This class implements a default bean introspection algorithm based on the JDK classes in the {@link java.beans} package. It discovers properties conforming
  33.  * to the Java Beans specification.
  34.  * </p>
  35.  * <p>
  36.  * This class is a singleton. The single instance can be obtained using the {@code INSTANCE} field. It does not define any state and thus can be shared by
  37.  * arbitrary clients. {@link PropertyUtils} per default uses this instance as its only {@code BeanIntrospector} object.
  38.  * </p>
  39.  *
  40.  * @since 1.9
  41.  */
  42. public class DefaultBeanIntrospector implements BeanIntrospector {

  43.     /** The singleton instance of this class. */
  44.     public static final BeanIntrospector INSTANCE = new DefaultBeanIntrospector();

  45.     /** Constant for arguments types of a method that expects a list argument. */
  46.     private static final Class<?>[] LIST_CLASS_PARAMETER = new Class[] { java.util.List.class };

  47.     /** For logging. Each subclass gets its own log instance. */
  48.     private final Log log = LogFactory.getLog(getClass());

  49.     /**
  50.      * Private constructor so that no instances can be created.
  51.      */
  52.     private DefaultBeanIntrospector() {
  53.     }

  54.     /**
  55.      * This method fixes an issue where IndexedPropertyDescriptor behaves differently in different versions of the JDK for 'indexed' properties which use
  56.      * java.util.List (rather than an array). It implements a workaround for Bug 28358. If you have a Bean with the following getters/setters for an indexed
  57.      * property:
  58.      *
  59.      * <pre>
  60.      * public List getFoo()
  61.      * public Object getFoo(int index)
  62.      * public void setFoo(List foo)
  63.      * public void setFoo(int index, Object foo)
  64.      * </pre>
  65.      *
  66.      * then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod() behave as follows:
  67.      * <ul>
  68.      * <li>JDK 1.3.1_04: returns valid Method objects from these methods.</li>
  69.      * <li>JDK 1.4.2_05: returns null from these methods.</li>
  70.      * </ul>
  71.      *
  72.      * @param beanClass   the current class to be inspected
  73.      * @param descriptors the array with property descriptors
  74.      */
  75.     private void handleIndexedPropertyDescriptors(final Class<?> beanClass, final PropertyDescriptor[] descriptors) {
  76.         for (final PropertyDescriptor pd : descriptors) {
  77.             if (pd instanceof IndexedPropertyDescriptor) {
  78.                 final IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) pd;
  79.                 final String propName = descriptor.getName().substring(0, 1).toUpperCase() + descriptor.getName().substring(1);

  80.                 if (descriptor.getReadMethod() == null) {
  81.                     final String methodName = descriptor.getIndexedReadMethod() != null ? descriptor.getIndexedReadMethod().getName() : "get" + propName;
  82.                     final Method readMethod = MethodUtils.getMatchingAccessibleMethod(beanClass, methodName, BeanUtils.EMPTY_CLASS_ARRAY);
  83.                     if (readMethod != null) {
  84.                         try {
  85.                             descriptor.setReadMethod(readMethod);
  86.                         } catch (final Exception e) {
  87.                             log.error("Error setting indexed property read method", e);
  88.                         }
  89.                     }
  90.                 }
  91.                 if (descriptor.getWriteMethod() == null) {
  92.                     final String methodName = descriptor.getIndexedWriteMethod() != null ? descriptor.getIndexedWriteMethod().getName() : "set" + propName;
  93.                     Method writeMethod = MethodUtils.getMatchingAccessibleMethod(beanClass, methodName, LIST_CLASS_PARAMETER);
  94.                     if (writeMethod == null) {
  95.                         for (final Method m : beanClass.getMethods()) {
  96.                             if (m.getName().equals(methodName)) {
  97.                                 final Class<?>[] parameterTypes = m.getParameterTypes();
  98.                                 if (parameterTypes.length == 1 && List.class.isAssignableFrom(parameterTypes[0])) {
  99.                                     writeMethod = m;
  100.                                     break;
  101.                                 }
  102.                             }
  103.                         }
  104.                     }
  105.                     if (writeMethod != null) {
  106.                         try {
  107.                             descriptor.setWriteMethod(writeMethod);
  108.                         } catch (final Exception e) {
  109.                             log.error("Error setting indexed property write method", e);
  110.                         }
  111.                     }
  112.                 }
  113.             }
  114.         }
  115.     }

  116.     /**
  117.      * Performs introspection of a specific Java class. This implementation uses the {@code java.beans.Introspector.getBeanInfo()} method to obtain all property
  118.      * descriptors for the current class and adds them to the passed in introspection context.
  119.      *
  120.      * @param icontext the introspection context
  121.      */
  122.     @Override
  123.     public void introspect(final IntrospectionContext icontext) {
  124.         BeanInfo beanInfo = null;
  125.         try {
  126.             beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
  127.         } catch (final IntrospectionException e) {
  128.             // no descriptors are added to the context
  129.             log.error("Error when inspecting class " + icontext.getTargetClass(), e);
  130.             return;
  131.         }

  132.         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
  133.         if (descriptors == null) {
  134.             descriptors = PropertyDescriptors.EMPTY_ARRAY;
  135.         }

  136.         handleIndexedPropertyDescriptors(icontext.getTargetClass(), descriptors);
  137.         icontext.addPropertyDescriptors(descriptors);
  138.     }
  139. }