001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.beanutils; 018 019import java.beans.BeanInfo; 020import java.beans.IndexedPropertyDescriptor; 021import java.beans.IntrospectionException; 022import java.beans.Introspector; 023import java.beans.PropertyDescriptor; 024import java.lang.reflect.Method; 025import java.util.List; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030/** 031 * <p> 032 * The default {@link BeanIntrospector} implementation. 033 * </p> 034 * <p> 035 * This class implements a default bean introspection algorithm based on the JDK 036 * classes in the <code>java.beans</code> package. It discovers properties 037 * conforming to the Java Beans specification. 038 * </p> 039 * <p> 040 * This class is a singleton. The single instance can be obtained using the 041 * {@code INSTANCE} field. It does not define any state and thus can be 042 * shared by arbitrary clients. {@link PropertyUtils} per default uses this 043 * instance as its only {@code BeanIntrospector} object. 044 * </p> 045 * 046 * @since 1.9 047 */ 048public class DefaultBeanIntrospector implements BeanIntrospector { 049 /** The singleton instance of this class. */ 050 public static final BeanIntrospector INSTANCE = new DefaultBeanIntrospector(); 051 052 /** Constant for argument types of a method that expects no arguments. */ 053 private static final Class<?>[] EMPTY_CLASS_PARAMETERS = new Class[0]; 054 055 /** Constant for arguments types of a method that expects a list argument. */ 056 private static final Class<?>[] LIST_CLASS_PARAMETER = new Class[] { List.class }; 057 058 /** Log instance */ 059 private final Log log = LogFactory.getLog(getClass()); 060 061 /** 062 * Private constructor so that no instances can be created. 063 */ 064 private DefaultBeanIntrospector() { 065 } 066 067 /** 068 * This method fixes an issue where IndexedPropertyDescriptor behaves 069 * differently in different versions of the JDK for 'indexed' properties 070 * which use java.util.List (rather than an array). It implements a 071 * workaround for Bug 28358. If you have a Bean with the following 072 * getters/setters for an indexed property: 073 * 074 * <pre> 075 * public List getFoo() 076 * public Object getFoo(int index) 077 * public void setFoo(List foo) 078 * public void setFoo(int index, Object foo) 079 * </pre> 080 * 081 * then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod() 082 * behave as follows: 083 * <ul> 084 * <li>JDK 1.3.1_04: returns valid Method objects from these methods.</li> 085 * <li>JDK 1.4.2_05: returns null from these methods.</li> 086 * </ul> 087 * 088 * @param beanClass the current class to be inspected 089 * @param descriptors the array with property descriptors 090 */ 091 private void handleIndexedPropertyDescriptors(final Class<?> beanClass, 092 final PropertyDescriptor[] descriptors) { 093 for (final PropertyDescriptor pd : descriptors) { 094 if (pd instanceof IndexedPropertyDescriptor) { 095 final IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) pd; 096 final String propName = descriptor.getName().substring(0, 1) 097 .toUpperCase() 098 + descriptor.getName().substring(1); 099 100 if (descriptor.getReadMethod() == null) { 101 final String methodName = descriptor.getIndexedReadMethod() != null ? descriptor 102 .getIndexedReadMethod().getName() : "get" 103 + propName; 104 final Method readMethod = MethodUtils 105 .getMatchingAccessibleMethod(beanClass, methodName, 106 EMPTY_CLASS_PARAMETERS); 107 if (readMethod != null) { 108 try { 109 descriptor.setReadMethod(readMethod); 110 } catch (final Exception e) { 111 log.error( 112 "Error setting indexed property read method", 113 e); 114 } 115 } 116 } 117 if (descriptor.getWriteMethod() == null) { 118 final String methodName = descriptor.getIndexedWriteMethod() != null ? descriptor 119 .getIndexedWriteMethod().getName() : "set" 120 + propName; 121 Method writeMethod = MethodUtils 122 .getMatchingAccessibleMethod(beanClass, methodName, 123 LIST_CLASS_PARAMETER); 124 if (writeMethod == null) { 125 for (final Method m : beanClass.getMethods()) { 126 if (m.getName().equals(methodName)) { 127 final Class<?>[] parameterTypes = m.getParameterTypes(); 128 if (parameterTypes.length == 1 129 && List.class 130 .isAssignableFrom(parameterTypes[0])) { 131 writeMethod = m; 132 break; 133 } 134 } 135 } 136 } 137 if (writeMethod != null) { 138 try { 139 descriptor.setWriteMethod(writeMethod); 140 } catch (final Exception e) { 141 log.error( 142 "Error setting indexed property write method", 143 e); 144 } 145 } 146 } 147 } 148 } 149 } 150 151 /** 152 * Performs introspection of a specific Java class. This implementation uses 153 * the {@code java.beans.Introspector.getBeanInfo()} method to obtain 154 * all property descriptors for the current class and adds them to the 155 * passed in introspection context. 156 * 157 * @param icontext the introspection context 158 */ 159 @Override 160 public void introspect(final IntrospectionContext icontext) { 161 BeanInfo beanInfo = null; 162 try { 163 beanInfo = Introspector.getBeanInfo(icontext.getTargetClass()); 164 } catch (final IntrospectionException e) { 165 // no descriptors are added to the context 166 log.error( 167 "Error when inspecting class " + icontext.getTargetClass(), 168 e); 169 return; 170 } 171 172 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); 173 if (descriptors == null) { 174 descriptors = new PropertyDescriptor[0]; 175 } 176 177 handleIndexedPropertyDescriptors(icontext.getTargetClass(), 178 descriptors); 179 icontext.addPropertyDescriptors(descriptors); 180 } 181}