blob: 8483f9fde34a45b949c5cc061e970a79a2dc02df [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.beanutils2;
import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* The default {@link BeanIntrospector} implementation.
* </p>
* <p>
* This class implements a default bean introspection algorithm based on the JDK
* classes in the {@code java.beans} package. It discovers properties
* conforming to the Java Beans specification.
* </p>
* <p>
* 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 arbitrary clients. {@link PropertyUtils} per default uses this
* instance as its only {@code BeanIntrospector} object.
* </p>
*
* @since 1.9
*/
public class DefaultBeanIntrospector implements BeanIntrospector {
/** The singleton instance of this class. */
public static final BeanIntrospector INSTANCE = new DefaultBeanIntrospector();
/** Constant for argument types of a method that expects no arguments. */
private static final Class<?>[] EMPTY_CLASS_PARAMETERS = new Class[0];
/** Constant for arguments types of a method that expects a list argument. */
private static final Class<?>[] LIST_CLASS_PARAMETER = new Class[] { java.util.List.class };
/** Log instance */
private final Log log = LogFactory.getLog(getClass());
/**
* Private constructor so that no instances can be created.
*/
private DefaultBeanIntrospector() {
}
/**
* Performs introspection of a specific Java class. This implementation uses
* the {@code java.beans.Introspector.getBeanInfo()} method to obtain
* all property descriptors for the current class and adds them to the
* passed in introspection context.
*
* @param icontext the introspection context
*/
@Override
public void introspect(final IntrospectionContext icontext) {
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
} catch (final IntrospectionException e) {
// no descriptors are added to the context
log.error(
"Error when inspecting class " + icontext.getTargetClass(),
e);
return;
}
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
if (descriptors == null) {
descriptors = new PropertyDescriptor[0];
}
handleIndexedPropertyDescriptors(icontext.getTargetClass(),
descriptors);
icontext.addPropertyDescriptors(descriptors);
}
/**
* This method fixes an issue where IndexedPropertyDescriptor behaves
* differently in different versions of the JDK for 'indexed' properties
* which use 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 property:
*
* <pre>
* public List getFoo()
* public Object getFoo(int index)
* public void setFoo(List foo)
* public void setFoo(int index, Object foo)
* </pre>
*
* then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
* behave as follows:
* <ul>
* <li>JDK 1.3.1_04: returns valid Method objects from these methods.</li>
* <li>JDK 1.4.2_05: returns null from these methods.</li>
* </ul>
*
* @param beanClass the current class to be inspected
* @param descriptors the array with property descriptors
*/
private void handleIndexedPropertyDescriptors(final Class<?> beanClass,
final PropertyDescriptor[] descriptors) {
for (final PropertyDescriptor pd : descriptors) {
if (pd instanceof IndexedPropertyDescriptor) {
final IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) pd;
final String propName = descriptor.getName().substring(0, 1)
.toUpperCase()
+ descriptor.getName().substring(1);
if (descriptor.getReadMethod() == null) {
final String methodName = descriptor.getIndexedReadMethod() != null ? descriptor
.getIndexedReadMethod().getName() : "get"
+ propName;
final Method readMethod = MethodUtils
.getMatchingAccessibleMethod(beanClass, methodName,
EMPTY_CLASS_PARAMETERS);
if (readMethod != null) {
try {
descriptor.setReadMethod(readMethod);
} catch (final Exception e) {
log.error(
"Error setting indexed property read method",
e);
}
}
}
if (descriptor.getWriteMethod() == null) {
final String methodName = descriptor.getIndexedWriteMethod() != null ? descriptor
.getIndexedWriteMethod().getName() : "set"
+ propName;
Method writeMethod = MethodUtils
.getMatchingAccessibleMethod(beanClass, methodName,
LIST_CLASS_PARAMETER);
if (writeMethod == null) {
for (final Method m : beanClass.getMethods()) {
if (m.getName().equals(methodName)) {
final Class<?>[] parameterTypes = m.getParameterTypes();
if (parameterTypes.length == 1
&& List.class
.isAssignableFrom(parameterTypes[0])) {
writeMethod = m;
break;
}
}
}
}
if (writeMethod != null) {
try {
descriptor.setWriteMethod(writeMethod);
} catch (final Exception e) {
log.error(
"Error setting indexed property write method",
e);
}
}
}
}
}
}
}