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 * https://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[] { 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 }