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  package org.apache.commons.beanutils2;
18  
19  import java.beans.BeanInfo;
20  import java.beans.IndexedPropertyDescriptor;
21  import java.beans.IntrospectionException;
22  import java.beans.Introspector;
23  import java.beans.PropertyDescriptor;
24  import java.lang.reflect.Method;
25  import java.util.List;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  /**
31   * <p>
32   * The default {@link BeanIntrospector} implementation.
33   * </p>
34   * <p>
35   * This class implements a default bean introspection algorithm based on the JDK classes in the {@link java.beans} package. It discovers properties conforming
36   * to the Java Beans specification.
37   * </p>
38   * <p>
39   * 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
40   * arbitrary clients. {@link PropertyUtils} per default uses this instance as its only {@code BeanIntrospector} object.
41   * </p>
42   *
43   * @since 1.9
44   */
45  public class DefaultBeanIntrospector implements BeanIntrospector {
46  
47      /** The singleton instance of this class. */
48      public static final BeanIntrospector INSTANCE = new DefaultBeanIntrospector();
49  
50      /** Constant for arguments types of a method that expects a list argument. */
51      private static final Class<?>[] LIST_CLASS_PARAMETER = new Class[] { java.util.List.class };
52  
53      /** For logging. Each subclass gets its own log instance. */
54      private final Log log = LogFactory.getLog(getClass());
55  
56      /**
57       * Private constructor so that no instances can be created.
58       */
59      private DefaultBeanIntrospector() {
60      }
61  
62      /**
63       * This method fixes an issue where IndexedPropertyDescriptor behaves differently in different versions of the JDK for 'indexed' properties which use
64       * 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
65       * property:
66       *
67       * <pre>
68       * public List getFoo()
69       * public Object getFoo(int index)
70       * public void setFoo(List foo)
71       * public void setFoo(int index, Object foo)
72       * </pre>
73       *
74       * then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod() behave as follows:
75       * <ul>
76       * <li>JDK 1.3.1_04: returns valid Method objects from these methods.</li>
77       * <li>JDK 1.4.2_05: returns null from these methods.</li>
78       * </ul>
79       *
80       * @param beanClass   the current class to be inspected
81       * @param descriptors the array with property descriptors
82       */
83      private void handleIndexedPropertyDescriptors(final Class<?> beanClass, final PropertyDescriptor[] descriptors) {
84          for (final PropertyDescriptor pd : descriptors) {
85              if (pd instanceof IndexedPropertyDescriptor) {
86                  final IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) pd;
87                  final String propName = descriptor.getName().substring(0, 1).toUpperCase() + descriptor.getName().substring(1);
88  
89                  if (descriptor.getReadMethod() == null) {
90                      final String methodName = descriptor.getIndexedReadMethod() != null ? descriptor.getIndexedReadMethod().getName() : "get" + propName;
91                      final Method readMethod = MethodUtils.getMatchingAccessibleMethod(beanClass, methodName, BeanUtils.EMPTY_CLASS_ARRAY);
92                      if (readMethod != null) {
93                          try {
94                              descriptor.setReadMethod(readMethod);
95                          } catch (final Exception e) {
96                              log.error("Error setting indexed property read method", e);
97                          }
98                      }
99                  }
100                 if (descriptor.getWriteMethod() == null) {
101                     final String methodName = descriptor.getIndexedWriteMethod() != null ? descriptor.getIndexedWriteMethod().getName() : "set" + propName;
102                     Method writeMethod = MethodUtils.getMatchingAccessibleMethod(beanClass, methodName, LIST_CLASS_PARAMETER);
103                     if (writeMethod == null) {
104                         for (final Method m : beanClass.getMethods()) {
105                             if (m.getName().equals(methodName)) {
106                                 final Class<?>[] parameterTypes = m.getParameterTypes();
107                                 if (parameterTypes.length == 1 && List.class.isAssignableFrom(parameterTypes[0])) {
108                                     writeMethod = m;
109                                     break;
110                                 }
111                             }
112                         }
113                     }
114                     if (writeMethod != null) {
115                         try {
116                             descriptor.setWriteMethod(writeMethod);
117                         } catch (final Exception e) {
118                             log.error("Error setting indexed property write method", e);
119                         }
120                     }
121                 }
122             }
123         }
124     }
125 
126     /**
127      * Performs introspection of a specific Java class. This implementation uses the {@code java.beans.Introspector.getBeanInfo()} method to obtain all property
128      * descriptors for the current class and adds them to the passed in introspection context.
129      *
130      * @param icontext the introspection context
131      */
132     @Override
133     public void introspect(final IntrospectionContext icontext) {
134         BeanInfo beanInfo = null;
135         try {
136             beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
137         } catch (final IntrospectionException e) {
138             // no descriptors are added to the context
139             log.error("Error when inspecting class " + icontext.getTargetClass(), e);
140             return;
141         }
142 
143         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
144         if (descriptors == null) {
145             descriptors = PropertyDescriptors.EMPTY_ARRAY;
146         }
147 
148         handleIndexedPropertyDescriptors(icontext.getTargetClass(), descriptors);
149         icontext.addPropertyDescriptors(descriptors);
150     }
151 }