diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java new file mode 100644 index 0000000000..ba9e309ace --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -0,0 +1,1058 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed 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.springframework.beans; + +import java.beans.PropertyChangeEvent; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.PrivilegedActionException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.CollectionFactory; +import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.ConverterNotFoundException; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.lang.UsesJava8; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * A basic {@link ConfigurablePropertyAccessor} that provides the necessary + * infrastructure for all typical use cases. + * + *

This accessor will convert collection and array values to the corresponding + * target collections or arrays, if necessary. Custom property editors that deal + * with collections or arrays can either be written via PropertyEditor's + * {@code setValue}, or against a comma-delimited String via {@code setAsText}, + * as String arrays are converted in such a format if the array itself is not + * assignable. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @author Stephane Nicoll + * @since 4.2 + * @see #registerCustomEditor + * @see #setPropertyValues + * @see #setPropertyValue + * @see #getPropertyValue + * @see #getPropertyType + * @see BeanWrapper + * @see PropertyEditorRegistrySupport + */ +public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor { + + /** + * We'll create a lot of these objects, so we don't want a new logger every time. + */ + private static final Log logger = LogFactory.getLog(AbstractNestablePropertyAccessor.class); + + private static Class javaUtilOptionalClass = null; + + static { + try { + javaUtilOptionalClass = + ClassUtils.forName("java.util.Optional", AbstractNestablePropertyAccessor.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + // Java 8 not available - Optional references simply not supported then. + } + } + + private int autoGrowCollectionLimit = Integer.MAX_VALUE; + + /** The wrapped object */ + private Object object; + + private String nestedPath = ""; + + private Object rootObject; + + /** + * Map with cached nested Accessors: nested path -> Accessor instance. + */ + private Map nestedPropertyAccessors; + + /** + * Create new empty accessor. Wrapped instance needs to be set afterwards. + * Registers default editors. + * @see #setWrappedInstance + */ + protected AbstractNestablePropertyAccessor() { + this(true); + } + + /** + * Create new empty accessor. Wrapped instance needs to be set afterwards. + * @param registerDefaultEditors whether to register default editors + * (can be suppressed if the accessor won't need any type conversion) + * @see #setWrappedInstance + */ + protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) { + if (registerDefaultEditors) { + registerDefaultEditors(); + } + this.typeConverterDelegate = new TypeConverterDelegate(this); + } + + /** + * Create new accessor for the given object. + * @param object object wrapped by this accessor + */ + protected AbstractNestablePropertyAccessor(Object object) { + registerDefaultEditors(); + setWrappedInstance(object); + } + + /** + * Create new accessor, wrapping a new instance of the specified class. + * @param clazz class to instantiate and wrap + */ + protected AbstractNestablePropertyAccessor(Class clazz) { + registerDefaultEditors(); + setWrappedInstance(BeanUtils.instantiateClass(clazz)); + } + + /** + * Create new accessor for the given object, + * registering a nested path that the object is in. + * @param object object wrapped by this accessor + * @param nestedPath the nested path of the object + * @param rootObject the root object at the top of the path + */ + protected AbstractNestablePropertyAccessor(Object object, String nestedPath, Object rootObject) { + registerDefaultEditors(); + setWrappedInstance(object, nestedPath, rootObject); + } + + /** + * Create new accessor for the given object, + * registering a nested path that the object is in. + * @param object object wrapped by this accessor + * @param nestedPath the nested path of the object + * @param parent the containing accessor (must not be {@code null}) + */ + protected AbstractNestablePropertyAccessor(Object object, String nestedPath, AbstractNestablePropertyAccessor parent) { + setWrappedInstance(object, nestedPath, parent.getWrappedInstance()); + setExtractOldValueForEditor(parent.isExtractOldValueForEditor()); + setAutoGrowNestedPaths(parent.isAutoGrowNestedPaths()); + setAutoGrowCollectionLimit(parent.getAutoGrowCollectionLimit()); + setConversionService(parent.getConversionService()); + } + + /** + * Specify a limit for array and collection auto-growing. + *

Default is unlimited on a plain accessor. + */ + public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) { + this.autoGrowCollectionLimit = autoGrowCollectionLimit; + } + + /** + * Return the limit for array and collection auto-growing. + */ + public int getAutoGrowCollectionLimit() { + return this.autoGrowCollectionLimit; + } + + /** + * Switch the target object, replacing the cached introspection results only + * if the class of the new object is different to that of the replaced object. + * @param object the new target object + */ + public void setWrappedInstance(Object object) { + setWrappedInstance(object, "", null); + } + + /** + * Switch the target object, replacing the cached introspection results only + * if the class of the new object is different to that of the replaced object. + * @param object the new target object + * @param nestedPath the nested path of the object + * @param rootObject the root object at the top of the path + */ + public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { + Assert.notNull(object, "Bean object must not be null"); + if (object.getClass().equals(javaUtilOptionalClass)) { + this.object = OptionalUnwrapper.unwrap(object); + } + else { + this.object = object; + } + this.nestedPath = (nestedPath != null ? nestedPath : ""); + this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object); + this.nestedPropertyAccessors = null; + this.typeConverterDelegate = new TypeConverterDelegate(this, this.object); + } + + public final Object getWrappedInstance() { + return this.object; + } + + public final Class getWrappedClass() { + return (this.object != null ? this.object.getClass() : null); + } + + /** + * Return the nested path of the object wrapped by this accessor. + */ + public final String getNestedPath() { + return this.nestedPath; + } + + /** + * Return the root object at the top of the path of this accessor. + * @see #getNestedPath + */ + public final Object getRootInstance() { + return this.rootObject; + } + + /** + * Return the class of the root object at the top of the path of this accessor. + * @see #getNestedPath + */ + public final Class getRootClass() { + return (this.rootObject != null ? this.rootObject.getClass() : null); + } + + @Override + public void setPropertyValue(String propertyName, Object value) throws BeansException { + AbstractNestablePropertyAccessor nestedPa; + try { + nestedPa = getPropertyAccessorForPropertyPath(propertyName); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, + "Nested property in path '" + propertyName + "' does not exist", ex); + } + PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); + nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value)); + } + + @Override + public void setPropertyValue(PropertyValue pv) throws BeansException { + PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens; + if (tokens == null) { + String propertyName = pv.getName(); + AbstractNestablePropertyAccessor nestedPa; + try { + nestedPa = getPropertyAccessorForPropertyPath(propertyName); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, + "Nested property in path '" + propertyName + "' does not exist", ex); + } + tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); + if (nestedPa == this) { + pv.getOriginalPropertyValue().resolvedTokens = tokens; + } + nestedPa.setPropertyValue(tokens, pv); + } + else { + setPropertyValue(tokens, pv); + } + } + + @SuppressWarnings("unchecked") + protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { + String propertyName = tokens.canonicalName; + String actualName = tokens.actualName; + + if (tokens.keys != null) { + // Apply indexes and map keys: fetch value for all keys but the last one. + PropertyTokenHolder getterTokens = new PropertyTokenHolder(); + getterTokens.canonicalName = tokens.canonicalName; + getterTokens.actualName = tokens.actualName; + getterTokens.keys = new String[tokens.keys.length - 1]; + System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); + Object propValue; + try { + propValue = getPropertyValue(getterTokens); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value in property referenced " + + "in indexed property path '" + propertyName + "'", ex); + } + // Set value for last key. + String key = tokens.keys[tokens.keys.length - 1]; + if (propValue == null) { + // null map value case + if (isAutoGrowNestedPaths()) { + // TODO: cleanup, this is pretty hacky + int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); + getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex); + propValue = setDefaultValue(getterTokens); + } + else { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value in property referenced " + + "in indexed property path '" + propertyName + "': returned null"); + } + } + if (propValue.getClass().isArray()) { + PropertyHandler ph = getLocalPropertyHandler(actualName); + Class requiredType = propValue.getClass().getComponentType(); + int arrayIndex = Integer.parseInt(key); + Object oldValue = null; + try { + if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { + oldValue = Array.get(propValue, arrayIndex); + } + Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), + requiredType, ph.nested(tokens.keys.length)); + int length = Array.getLength(propValue); + if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) { + Class componentType = propValue.getClass().getComponentType(); + Object newArray = Array.newInstance(componentType, arrayIndex + 1); + System.arraycopy(propValue, 0, newArray, 0, length); + setPropertyValue(actualName, newArray); + propValue = getPropertyValue(actualName); + } + Array.set(propValue, arrayIndex, convertedValue); + } + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid array index in property path '" + propertyName + "'", ex); + } + } + else if (propValue instanceof List) { + PropertyHandler ph = getPropertyHandler(actualName); + Class requiredType = ph.getCollectionType(tokens.keys.length); + List list = (List) propValue; + int index = Integer.parseInt(key); + Object oldValue = null; + if (isExtractOldValueForEditor() && index < list.size()) { + oldValue = list.get(index); + } + Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), + requiredType, ph.nested(tokens.keys.length)); + int size = list.size(); + if (index >= size && index < this.autoGrowCollectionLimit) { + for (int i = size; i < index; i++) { + try { + list.add(null); + } + catch (NullPointerException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot set element with index " + index + " in List of size " + + size + ", accessed using property path '" + propertyName + + "': List does not support filling up gaps with null elements"); + } + } + list.add(convertedValue); + } + else { + try { + list.set(index, convertedValue); + } + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid list index in property path '" + propertyName + "'", ex); + } + } + } + else if (propValue instanceof Map) { + PropertyHandler ph = getLocalPropertyHandler(actualName); + Class mapKeyType = ph.getMapKeyType(tokens.keys.length); + Class mapValueType = ph.getMapValueType(tokens.keys.length); + Map map = (Map) propValue; + // IMPORTANT: Do not pass full property name in here - property editors + // must not kick in for map keys but rather only for map values. + TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); + Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); + Object oldValue = null; + if (isExtractOldValueForEditor()) { + oldValue = map.get(convertedMapKey); + } + // Pass full property name and old value in here, since we want full + // conversion ability for map values. + Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), + mapValueType, ph.nested(tokens.keys.length)); + map.put(convertedMapKey, convertedMapValue); + } + else { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Property referenced in indexed property path '" + propertyName + + "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); + } + } + + else { + PropertyHandler ph = getLocalPropertyHandler(actualName); + if (ph == null || !ph.isWritable()) { + if (pv.isOptional()) { + if (logger.isDebugEnabled()) { + logger.debug("Ignoring optional value for property '" + actualName + + "' - property not found on bean class [" + getRootClass().getName() + "]"); + } + return; + } + else { + throw createNotWritablePropertyException(propertyName); + } + } + Object oldValue = null; + try { + Object originalValue = pv.getValue(); + Object valueToApply = originalValue; + if (!Boolean.FALSE.equals(pv.conversionNecessary)) { + if (pv.isConverted()) { + valueToApply = pv.getConvertedValue(); + } + else { + if (isExtractOldValueForEditor() && ph.isReadable()) { + try { + oldValue = ph.getValue(); + } + catch (Exception ex) { + if (ex instanceof PrivilegedActionException) { + ex = ((PrivilegedActionException) ex).getException(); + } + if (logger.isDebugEnabled()) { + logger.debug("Could not read previous value of property '" + + this.nestedPath + propertyName + "'", ex); + } + } + } + valueToApply = convertForProperty( + propertyName, oldValue, originalValue, ph.toTypeDescriptor()); + } + pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); + } + ph.setValue(object, valueToApply); + } + catch (TypeMismatchException ex) { + throw ex; + } + catch (InvocationTargetException ex) { + PropertyChangeEvent propertyChangeEvent = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + if (ex.getTargetException() instanceof ClassCastException) { + throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); + } + else { + Throwable cause = ex.getTargetException(); + if (cause instanceof UndeclaredThrowableException) { + // May happen e.g. with Groovy-generated methods + cause = cause.getCause(); + } + throw new MethodInvocationException(propertyChangeEvent, cause); + } + } + catch (Exception ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new MethodInvocationException(pce, ex); + } + } + } + + @Override + public Class getPropertyType(String propertyName) throws BeansException { + try { + PropertyHandler ph = getPropertyHandler(propertyName); + if (ph != null) { + return ph.getPropertyType(); + } + else { + // Maybe an indexed/mapped property... + Object value = getPropertyValue(propertyName); + if (value != null) { + return value.getClass(); + } + // Check to see if there is a custom editor, + // which might give an indication on the desired target type. + Class editorType = guessPropertyTypeFromEditors(propertyName); + if (editorType != null) { + return editorType; + } + } + } + catch (InvalidPropertyException ex) { + // Consider as not determinable. + } + return null; + } + + @Override + public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { + try { + AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); + String finalPath = getFinalPath(nestedPa, propertyName); + PropertyTokenHolder tokens = getPropertyNameTokens(finalPath); + PropertyHandler ph = nestedPa.getLocalPropertyHandler(tokens.actualName); + if (ph != null) { + if (tokens.keys != null) { + if (ph.isReadable() || ph.isWritable()) { + return ph.nested(tokens.keys.length); + } + } + else { + if (ph.isReadable() || ph.isWritable()) { + return ph.toTypeDescriptor(); + } + } + } + } + catch (InvalidPropertyException ex) { + // Consider as not determinable. + } + return null; + } + + @Override + public boolean isReadableProperty(String propertyName) { + try { + PropertyHandler ph = getPropertyHandler(propertyName); + if (ph != null) { + return ph.isReadable(); + } + else { + // Maybe an indexed/mapped property... + getPropertyValue(propertyName); + return true; + } + } + catch (InvalidPropertyException ex) { + // Cannot be evaluated, so can't be readable. + } + return false; + } + + @Override + public boolean isWritableProperty(String propertyName) { + try { + PropertyHandler ph = getPropertyHandler(propertyName); + if (ph != null) { + return ph.isWritable(); + } + else { + // Maybe an indexed/mapped property... + getPropertyValue(propertyName); + return true; + } + } + catch (InvalidPropertyException ex) { + // Cannot be evaluated, so can't be writable. + } + return false; + } + + private Object convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class requiredType, + TypeDescriptor td) throws TypeMismatchException { + try { + return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td); + } + catch (ConverterNotFoundException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); + throw new ConversionNotSupportedException(pce, td.getType(), ex); + } + catch (ConversionException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); + throw new TypeMismatchException(pce, requiredType, ex); + } + catch (IllegalStateException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); + throw new ConversionNotSupportedException(pce, requiredType, ex); + } + catch (IllegalArgumentException ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); + throw new TypeMismatchException(pce, requiredType, ex); + } + } + + protected Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td) + throws TypeMismatchException { + + return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td); + } + + @Override + public Object getPropertyValue(String propertyName) throws BeansException { + AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); + PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); + return nestedPa.getPropertyValue(tokens); + } + + @SuppressWarnings("unchecked") + protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { + String propertyName = tokens.canonicalName; + String actualName = tokens.actualName; + PropertyHandler ph = getLocalPropertyHandler(actualName); + if (ph == null || !ph.isReadable()) { + throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); + } + try { + Object value = ph.getValue(); + if (tokens.keys != null) { + if (value == null) { + if (isAutoGrowNestedPaths()) { + value = setDefaultValue(tokens.actualName); + } + else { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value of property referenced in indexed " + + "property path '" + propertyName + "': returned null"); + } + } + String indexedPropertyName = tokens.actualName; + // apply indexes and map keys + for (int i = 0; i < tokens.keys.length; i++) { + String key = tokens.keys[i]; + if (value == null) { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, + "Cannot access indexed value of property referenced in indexed " + + "property path '" + propertyName + "': returned null"); + } + else if (value.getClass().isArray()) { + int index = Integer.parseInt(key); + value = growArrayIfNecessary(value, index, indexedPropertyName); + value = Array.get(value, index); + } + else if (value instanceof List) { + int index = Integer.parseInt(key); + List list = (List) value; + growCollectionIfNecessary(list, index, indexedPropertyName, ph, i + 1); + value = list.get(index); + } + else if (value instanceof Set) { + // Apply index to Iterator in case of a Set. + Set set = (Set) value; + int index = Integer.parseInt(key); + if (index < 0 || index >= set.size()) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot get element with index " + index + " from Set of size " + + set.size() + ", accessed using property path '" + propertyName + "'"); + } + Iterator it = set.iterator(); + for (int j = 0; it.hasNext(); j++) { + Object elem = it.next(); + if (j == index) { + value = elem; + break; + } + } + } + else if (value instanceof Map) { + Map map = (Map) value; + Class mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0); + // IMPORTANT: Do not pass full property name in here - property editors + // must not kick in for map keys but rather only for map values. + TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); + Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); + value = map.get(convertedMapKey); + } + else { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Property referenced in indexed property path '" + propertyName + + "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]"); + } + indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX; + } + } + return value; + } + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Index of out of bounds in property path '" + propertyName + "'", ex); + } + catch (NumberFormatException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid index in property path '" + propertyName + "'", ex); + } + catch (TypeMismatchException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid index in property path '" + propertyName + "'", ex); + } + catch (InvocationTargetException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Getter for property '" + actualName + "' threw exception", ex); + } + catch (Exception ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Illegal attempt to get property '" + actualName + "' threw exception", ex); + } + } + + + /** + * Return the {@link PropertyHandler} for the specified {@code propertyName}, navigating + * if necessary. Return {@code null} if not found rather than throwing an exception. + * @param propertyName the property to obtain the descriptor for + * @return the property descriptor for the specified property, + * or {@code null} if not found + * @throws BeansException in case of introspection failure + */ + protected PropertyHandler getPropertyHandler(String propertyName) throws BeansException { + Assert.notNull(propertyName, "Property name must not be null"); + AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); + return nestedPa.getLocalPropertyHandler(getFinalPath(nestedPa, propertyName)); + } + + /** + * Return a {@link PropertyHandler} for the specified local {@code propertyName}. Only + * used to reach a property available in the current context. + * @param propertyName the name of a local property + * @return the handler for that property or {@code null} if it has not been found + */ + protected abstract PropertyHandler getLocalPropertyHandler(String propertyName); + + /** + * Create a new nested property accessor instance. + * Can be overridden in subclasses to create a PropertyAccessor subclass. + * @param object object wrapped by this PropertyAccessor + * @param nestedPath the nested path of the object + * @return the nested PropertyAccessor instance + */ + protected abstract AbstractNestablePropertyAccessor newNestedPropertyAccessor(Object object, String nestedPath); + + /** + * Create a {@link NotWritablePropertyException} for the specified property. + */ + protected abstract NotWritablePropertyException createNotWritablePropertyException(String propertyName); + + + private Object growArrayIfNecessary(Object array, int index, String name) { + if (!isAutoGrowNestedPaths()) { + return array; + } + int length = Array.getLength(array); + if (index >= length && index < this.autoGrowCollectionLimit) { + Class componentType = array.getClass().getComponentType(); + Object newArray = Array.newInstance(componentType, index + 1); + System.arraycopy(array, 0, newArray, 0, length); + for (int i = length; i < Array.getLength(newArray); i++) { + Array.set(newArray, i, newValue(componentType, null, name)); + } + // TODO this is not efficient because conversion may create a copy ... set directly because we know it is assignable. + setPropertyValue(name, newArray); + return getPropertyValue(name); + } + else { + return array; + } + } + + private void growCollectionIfNecessary(Collection collection, int index, String name, + PropertyHandler ph, int nestingLevel) { + + if (!isAutoGrowNestedPaths()) { + return; + } + int size = collection.size(); + if (index >= size && index < this.autoGrowCollectionLimit) { + Class elementType = ph.getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric(); + if (elementType != null) { + for (int i = collection.size(); i < index + 1; i++) { + collection.add(newValue(elementType, null, name)); + } + } + } + } + + /** + * Get the last component of the path. Also works if not nested. + * @param pa property accessor to work on + * @param nestedPath property path we know is nested + * @return last component of the path (the property on the target bean) + */ + private String getFinalPath(AbstractNestablePropertyAccessor pa, String nestedPath) { + if (pa == this) { + return nestedPath; + } + return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1); + } + + /** + * Recursively navigate to return a property accessor for the nested property path. + * @param propertyPath property property path, which may be nested + * @return a property accessor for the target bean + */ + @SuppressWarnings("unchecked") // avoid nested generic + protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) { + int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath); + // Handle nested properties recursively. + if (pos > -1) { + String nestedProperty = propertyPath.substring(0, pos); + String nestedPath = propertyPath.substring(pos + 1); + AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty); + return nestedPa.getPropertyAccessorForPropertyPath(nestedPath); + } + else { + return this; + } + } + + /** + * Retrieve a Property accessor for the given nested property. + * Create a new one if not found in the cache. + *

Note: Caching nested PropertyAccessors is necessary now, + * to keep registered custom editors for nested properties. + * @param nestedProperty property to create the PropertyAccessor for + * @return the PropertyAccessor instance, either cached or newly created + */ + private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) { + if (this.nestedPropertyAccessors == null) { + this.nestedPropertyAccessors = new HashMap(); + } + // Get value of bean property. + PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); + String canonicalName = tokens.canonicalName; + Object value = getPropertyValue(tokens); + if (value == null || (value.getClass().equals(javaUtilOptionalClass) && OptionalUnwrapper.isEmpty(value))) { + if (isAutoGrowNestedPaths()) { + value = setDefaultValue(tokens); + } + else { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName); + } + } + + // Lookup cached sub-PropertyAccessor, create new one if not found. + AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName); + if (nestedPa == null || nestedPa.getWrappedInstance() != + (value.getClass().equals(javaUtilOptionalClass) ? OptionalUnwrapper.unwrap(value) : value)) { + if (logger.isTraceEnabled()) { + logger.trace("Creating new nested " + getClass().getSimpleName() + " for property '" + canonicalName + "'"); + } + nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR); + // Inherit all type-specific PropertyEditors. + copyDefaultEditorsTo(nestedPa); + copyCustomEditorsTo(nestedPa, canonicalName); + this.nestedPropertyAccessors.put(canonicalName, nestedPa); + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Using cached nested property accessor for property '" + canonicalName + "'"); + } + } + return nestedPa; + } + + private Object setDefaultValue(String propertyName) { + PropertyTokenHolder tokens = new PropertyTokenHolder(); + tokens.actualName = propertyName; + tokens.canonicalName = propertyName; + return setDefaultValue(tokens); + } + + private Object setDefaultValue(PropertyTokenHolder tokens) { + PropertyValue pv = createDefaultPropertyValue(tokens); + setPropertyValue(tokens, pv); + return getPropertyValue(tokens); + } + + private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) { + TypeDescriptor desc = getPropertyTypeDescriptor(tokens.canonicalName); + Class type = desc.getType(); + if (type == null) { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Could not determine property type for auto-growing a default value"); + } + Object defaultValue = newValue(type, desc, tokens.canonicalName); + return new PropertyValue(tokens.canonicalName, defaultValue); + } + + private Object newValue(Class type, TypeDescriptor desc, String name) { + try { + if (type.isArray()) { + Class componentType = type.getComponentType(); + // TODO - only handles 2-dimensional arrays + if (componentType.isArray()) { + Object array = Array.newInstance(componentType, 1); + Array.set(array, 0, Array.newInstance(componentType.getComponentType(), 0)); + return array; + } + else { + return Array.newInstance(componentType, 0); + } + } + else if (Collection.class.isAssignableFrom(type)) { + TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null); + return CollectionFactory.createCollection(type, (elementDesc != null ? elementDesc.getType() : null), 16); + } + else if (Map.class.isAssignableFrom(type)) { + TypeDescriptor keyDesc = (desc != null ? desc.getMapKeyTypeDescriptor() : null); + return CollectionFactory.createMap(type, (keyDesc != null ? keyDesc.getType() : null), 16); + } + else { + return BeanUtils.instantiate(type); + } + } + catch (Exception ex) { + // TODO: Root cause exception context is lost here; just exception message preserved. + // Should we throw another exception type that preserves context instead? + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, + "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex); + } + } + + /** + * Parse the given property name into the corresponding property name tokens. + * @param propertyName the property name to parse + * @return representation of the parsed property tokens + */ + private PropertyTokenHolder getPropertyNameTokens(String propertyName) { + PropertyTokenHolder tokens = new PropertyTokenHolder(); + String actualName = null; + List keys = new ArrayList(2); + int searchIndex = 0; + while (searchIndex != -1) { + int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex); + searchIndex = -1; + if (keyStart != -1) { + int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length()); + if (keyEnd != -1) { + if (actualName == null) { + actualName = propertyName.substring(0, keyStart); + } + String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd); + if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { + key = key.substring(1, key.length() - 1); + } + keys.add(key); + searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length(); + } + } + } + tokens.actualName = (actualName != null ? actualName : propertyName); + tokens.canonicalName = tokens.actualName; + if (!keys.isEmpty()) { + tokens.canonicalName += + PROPERTY_KEY_PREFIX + + StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) + + PROPERTY_KEY_SUFFIX; + tokens.keys = StringUtils.toStringArray(keys); + } + return tokens; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(getClass().getName()); + if (this.object != null) { + sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]"); + } + else { + sb.append(": no wrapped object set"); + } + return sb.toString(); + } + + + /** + * Handle a given property. + */ + protected abstract static class PropertyHandler { + + private final Class propertyType; + + private final boolean readable; + + private final boolean writable; + + public PropertyHandler(Class propertyType, boolean readable, boolean writable) { + this.propertyType = propertyType; + this.readable = readable; + this.writable = writable; + } + + public Class getPropertyType() { + return this.propertyType; + } + + public boolean isReadable() { + return this.readable; + } + + public boolean isWritable() { + return this.writable; + } + + public abstract TypeDescriptor toTypeDescriptor(); + + public abstract ResolvableType getResolvableType(); + + public Class getMapKeyType(int nestingLevel) { + return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0); + } + + public Class getMapValueType(int nestingLevel) { + return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1); + } + + public Class getCollectionType(int nestingLevel) { + return getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric(); + } + + public abstract TypeDescriptor nested(int level); + + public abstract Object getValue() throws Exception; + + public abstract void setValue(Object object, Object value) throws Exception; + + } + + protected static class PropertyTokenHolder { + + public String canonicalName; + + public String actualName; + + public String[] keys; + } + + + /** + * Inner class to avoid a hard dependency on Java 8. + */ + @UsesJava8 + private static class OptionalUnwrapper { + + public static Object unwrap(Object optionalObject) { + Optional optional = (Optional) optionalObject; + Assert.isTrue(optional.isPresent(), "Optional value must be present"); + Object result = optional.get(); + Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported"); + return result; + } + + public static boolean isEmpty(Object optionalObject) { + return !((Optional) optionalObject).isPresent(); + } + } +} diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java index 45f8ed5ce8..078712179a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,164 +16,28 @@ package org.springframework.beans; -import java.beans.PropertyChangeEvent; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.UndeclaredThrowableException; -import java.security.PrivilegedActionException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.CollectionFactory; -import org.springframework.core.ResolvableType; -import org.springframework.core.convert.ConversionException; -import org.springframework.core.convert.ConverterNotFoundException; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.lang.UsesJava8; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; /** - * Abstract implementation of the {@link ConfigurablePropertyAccessor} interface. - * Provides the necessary infrastructure for all typical use cases. - * - *

This accessor will convert collection and array values to the corresponding - * target collections or arrays, if necessary. Custom property editors that deal - * with collections or arrays can either be written via PropertyEditor's - * {@code setValue}, or against a comma-delimited String via {@code setAsText}, - * as String arrays are converted in such a format if the array itself is not - * assignable. + * Abstract implementation of the {@link PropertyAccessor} interface. + * Provides base implementations of all convenience methods, with the + * implementation of actual property access left to subclasses. * - * @author Rod Johnson * @author Juergen Hoeller - * @author Rob Harrop * @author Stephane Nicoll * @since 2.0 - * @see #registerCustomEditor - * @see #setPropertyValues - * @see #setPropertyValue * @see #getPropertyValue - * @see #getPropertyType - * @see BeanWrapper - * @see PropertyEditorRegistrySupport + * @see #setPropertyValue */ public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor { - /** - * We'll create a lot of these objects, so we don't want a new logger every time. - */ - private static final Log logger = LogFactory.getLog(AbstractPropertyAccessor.class); - - private static Class javaUtilOptionalClass = null; - - static { - try { - javaUtilOptionalClass = - ClassUtils.forName("java.util.Optional", AbstractPropertyAccessor.class.getClassLoader()); - } - catch (ClassNotFoundException ex) { - // Java 8 not available - Optional references simply not supported then. - } - } - private boolean extractOldValueForEditor = false; private boolean autoGrowNestedPaths = false; - private int autoGrowCollectionLimit = Integer.MAX_VALUE; - - /** The wrapped object */ - private Object object; - - private String nestedPath = ""; - - private Object rootObject; - - /** - * Map with cached nested Accessors: nested path -> Accessor instance. - */ - private Map nestedPropertyAccessors; - - /** - * Create new empty accessor. Wrapped instance needs to be set afterwards. - * Registers default editors. - * @see #setWrappedInstance - */ - protected AbstractPropertyAccessor() { - this(true); - } - - /** - * Create new empty accessor. Wrapped instance needs to be set afterwards. - * @param registerDefaultEditors whether to register default editors - * (can be suppressed if the accessor won't need any type conversion) - * @see #setWrappedInstance - */ - protected AbstractPropertyAccessor(boolean registerDefaultEditors) { - if (registerDefaultEditors) { - registerDefaultEditors(); - } - this.typeConverterDelegate = new TypeConverterDelegate(this); - } - - /** - * Create new accessor for the given object. - * @param object object wrapped by this accessor - */ - protected AbstractPropertyAccessor(Object object) { - registerDefaultEditors(); - setWrappedInstance(object); - } - - /** - * Create new accessor, wrapping a new instance of the specified class. - * @param clazz class to instantiate and wrap - */ - protected AbstractPropertyAccessor(Class clazz) { - registerDefaultEditors(); - setWrappedInstance(BeanUtils.instantiateClass(clazz)); - } - - /** - * Create new accessor for the given object, - * registering a nested path that the object is in. - * @param object object wrapped by this accessor - * @param nestedPath the nested path of the object - * @param rootObject the root object at the top of the path - */ - protected AbstractPropertyAccessor(Object object, String nestedPath, Object rootObject) { - registerDefaultEditors(); - setWrappedInstance(object, nestedPath, rootObject); - } - - /** - * Create new accessor for the given object, - * registering a nested path that the object is in. - * @param object object wrapped by this accessor - * @param nestedPath the nested path of the object - * @param parent the containing accessor (must not be {@code null}) - */ - protected AbstractPropertyAccessor(Object object, String nestedPath, AbstractPropertyAccessor parent) { - setWrappedInstance(object, nestedPath, parent.getWrappedInstance()); - setExtractOldValueForEditor(parent.isExtractOldValueForEditor()); - setAutoGrowNestedPaths(parent.isAutoGrowNestedPaths()); - setAutoGrowCollectionLimit(parent.getAutoGrowCollectionLimit()); - setConversionService(parent.getConversionService()); - } - @Override public void setExtractOldValueForEditor(boolean extractOldValueForEditor) { @@ -195,127 +59,10 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl return this.autoGrowNestedPaths; } - /** - * Specify a limit for array and collection auto-growing. - *

Default is unlimited on a plain accessor. - */ - public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) { - this.autoGrowCollectionLimit = autoGrowCollectionLimit; - } - - /** - * Return the limit for array and collection auto-growing. - */ - public int getAutoGrowCollectionLimit() { - return this.autoGrowCollectionLimit; - } - - /** - * Switch the target object, replacing the cached introspection results only - * if the class of the new object is different to that of the replaced object. - * @param object the new target object - */ - public void setWrappedInstance(Object object) { - setWrappedInstance(object, "", null); - } - - /** - * Switch the target object, replacing the cached introspection results only - * if the class of the new object is different to that of the replaced object. - * @param object the new target object - * @param nestedPath the nested path of the object - * @param rootObject the root object at the top of the path - */ - public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { - Assert.notNull(object, "Bean object must not be null"); - if (object.getClass().equals(javaUtilOptionalClass)) { - this.object = OptionalUnwrapper.unwrap(object); - } - else { - this.object = object; - } - this.nestedPath = (nestedPath != null ? nestedPath : ""); - this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object); - this.nestedPropertyAccessors = null; - this.typeConverterDelegate = new TypeConverterDelegate(this, this.object); - } - - public final Object getWrappedInstance() { - return this.object; - } - - public final Class getWrappedClass() { - return (this.object != null ? this.object.getClass() : null); - } - - /** - * Return the nested path of the object wrapped by this accessor. - */ - public final String getNestedPath() { - return this.nestedPath; - } - - /** - * Return the root object at the top of the path of this accessor. - * @see #getNestedPath - */ - public final Object getRootInstance() { - return this.rootObject; - } - - /** - * Return the class of the root object at the top of the path of this accessor. - * @see #getNestedPath - */ - public final Class getRootClass() { - return (this.rootObject != null ? this.rootObject.getClass() : null); - } - - /** - * Actually set a property value. - * @param propertyName name of the property to set value of - * @param value the new value - * @throws InvalidPropertyException if there is no such property or - * if the property isn't writable - * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured - */ - @Override - public void setPropertyValue(String propertyName, Object value) throws BeansException { - AbstractPropertyAccessor nestedPa; - try { - nestedPa = getPropertyAccessorForPropertyPath(propertyName); - } - catch (NotReadablePropertyException ex) { - throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, - "Nested property in path '" + propertyName + "' does not exist", ex); - } - PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); - nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value)); - } @Override public void setPropertyValue(PropertyValue pv) throws BeansException { - PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens; - if (tokens == null) { - String propertyName = pv.getName(); - AbstractPropertyAccessor nestedPa; - try { - nestedPa = getPropertyAccessorForPropertyPath(propertyName); - } - catch (NotReadablePropertyException ex) { - throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, - "Nested property in path '" + propertyName + "' does not exist", ex); - } - tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); - if (nestedPa == this) { - pv.getOriginalPropertyValue().resolvedTokens = tokens; - } - nestedPa.setPropertyValue(tokens, pv); - } - else { - setPropertyValue(tokens, pv); - } + setPropertyValue(pv.getName(), pv.getValue()); } @Override @@ -375,325 +122,13 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl } } - @SuppressWarnings("unchecked") - protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { - String propertyName = tokens.canonicalName; - String actualName = tokens.actualName; - - if (tokens.keys != null) { - // Apply indexes and map keys: fetch value for all keys but the last one. - PropertyTokenHolder getterTokens = new PropertyTokenHolder(); - getterTokens.canonicalName = tokens.canonicalName; - getterTokens.actualName = tokens.actualName; - getterTokens.keys = new String[tokens.keys.length - 1]; - System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); - Object propValue; - try { - propValue = getPropertyValue(getterTokens); - } - catch (NotReadablePropertyException ex) { - throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value in property referenced " + - "in indexed property path '" + propertyName + "'", ex); - } - // Set value for last key. - String key = tokens.keys[tokens.keys.length - 1]; - if (propValue == null) { - // null map value case - if (isAutoGrowNestedPaths()) { - // TODO: cleanup, this is pretty hacky - int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); - getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex); - propValue = setDefaultValue(getterTokens); - } - else { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value in property referenced " + - "in indexed property path '" + propertyName + "': returned null"); - } - } - if (propValue.getClass().isArray()) { - PropertyHandler ph = getLocalPropertyHandler(actualName); - Class requiredType = propValue.getClass().getComponentType(); - int arrayIndex = Integer.parseInt(key); - Object oldValue = null; - try { - if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { - oldValue = Array.get(propValue, arrayIndex); - } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - requiredType, ph.nested(tokens.keys.length)); - int length = Array.getLength(propValue); - if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) { - Class componentType = propValue.getClass().getComponentType(); - Object newArray = Array.newInstance(componentType, arrayIndex + 1); - System.arraycopy(propValue, 0, newArray, 0, length); - setPropertyValue(actualName, newArray); - propValue = getPropertyValue(actualName); - } - Array.set(propValue, arrayIndex, convertedValue); - } - catch (IndexOutOfBoundsException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Invalid array index in property path '" + propertyName + "'", ex); - } - } - else if (propValue instanceof List) { - PropertyHandler ph = getPropertyHandler(actualName); - Class requiredType = ph.getCollectionType(tokens.keys.length); - List list = (List) propValue; - int index = Integer.parseInt(key); - Object oldValue = null; - if (isExtractOldValueForEditor() && index < list.size()) { - oldValue = list.get(index); - } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - requiredType, ph.nested(tokens.keys.length)); - int size = list.size(); - if (index >= size && index < this.autoGrowCollectionLimit) { - for (int i = size; i < index; i++) { - try { - list.add(null); - } - catch (NullPointerException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Cannot set element with index " + index + " in List of size " + - size + ", accessed using property path '" + propertyName + - "': List does not support filling up gaps with null elements"); - } - } - list.add(convertedValue); - } - else { - try { - list.set(index, convertedValue); - } - catch (IndexOutOfBoundsException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Invalid list index in property path '" + propertyName + "'", ex); - } - } - } - else if (propValue instanceof Map) { - PropertyHandler ph = getLocalPropertyHandler(actualName); - Class mapKeyType = ph.getMapKeyType(tokens.keys.length); - Class mapValueType = ph.getMapValueType(tokens.keys.length); - Map map = (Map) propValue; - // IMPORTANT: Do not pass full property name in here - property editors - // must not kick in for map keys but rather only for map values. - TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); - Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); - Object oldValue = null; - if (isExtractOldValueForEditor()) { - oldValue = map.get(convertedMapKey); - } - // Pass full property name and old value in here, since we want full - // conversion ability for map values. - Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - mapValueType, ph.nested(tokens.keys.length)); - map.put(convertedMapKey, convertedMapValue); - } - else { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Property referenced in indexed property path '" + propertyName + - "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); - } - } - - else { - PropertyHandler ph = getLocalPropertyHandler(actualName); - if (ph == null || !ph.isWritable()) { - if (pv.isOptional()) { - if (logger.isDebugEnabled()) { - logger.debug("Ignoring optional value for property '" + actualName + - "' - property not found on bean class [" + getRootClass().getName() + "]"); - } - return; - } - else { - throw createNotWritablePropertyException(propertyName); - } - } - Object oldValue = null; - try { - Object originalValue = pv.getValue(); - Object valueToApply = originalValue; - if (!Boolean.FALSE.equals(pv.conversionNecessary)) { - if (pv.isConverted()) { - valueToApply = pv.getConvertedValue(); - } - else { - if (isExtractOldValueForEditor() && ph.isReadable()) { - try { - oldValue = ph.getValue(); - } - catch (Exception ex) { - if (ex instanceof PrivilegedActionException) { - ex = ((PrivilegedActionException) ex).getException(); - } - if (logger.isDebugEnabled()) { - logger.debug("Could not read previous value of property '" + - this.nestedPath + propertyName + "'", ex); - } - } - } - valueToApply = convertForProperty( - propertyName, oldValue, originalValue, ph.toTypeDescriptor()); - } - pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); - } - ph.setValue(object, valueToApply); - } - catch (TypeMismatchException ex) { - throw ex; - } - catch (InvocationTargetException ex) { - PropertyChangeEvent propertyChangeEvent = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); - if (ex.getTargetException() instanceof ClassCastException) { - throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); - } - else { - Throwable cause = ex.getTargetException(); - if (cause instanceof UndeclaredThrowableException) { - // May happen e.g. with Groovy-generated methods - cause = cause.getCause(); - } - throw new MethodInvocationException(propertyChangeEvent, cause); - } - } - catch (Exception ex) { - PropertyChangeEvent pce = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); - throw new MethodInvocationException(pce, ex); - } - } - } - - @Override - public Class getPropertyType(String propertyName) throws BeansException { - try { - PropertyHandler ph = getPropertyHandler(propertyName); - if (ph != null) { - return ph.getPropertyType(); - } - else { - // Maybe an indexed/mapped property... - Object value = getPropertyValue(propertyName); - if (value != null) { - return value.getClass(); - } - // Check to see if there is a custom editor, - // which might give an indication on the desired target type. - Class editorType = guessPropertyTypeFromEditors(propertyName); - if (editorType != null) { - return editorType; - } - } - } - catch (InvalidPropertyException ex) { - // Consider as not determinable. - } - return null; - } + // Redefined with public visibility. @Override - public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { - try { - AbstractPropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); - String finalPath = getFinalPath(nestedPa, propertyName); - PropertyTokenHolder tokens = getPropertyNameTokens(finalPath); - PropertyHandler ph = nestedPa.getLocalPropertyHandler(tokens.actualName); - if (ph != null) { - if (tokens.keys != null) { - if (ph.isReadable() || ph.isWritable()) { - return ph.nested(tokens.keys.length); - } - } - else { - if (ph.isReadable() || ph.isWritable()) { - return ph.toTypeDescriptor(); - } - } - } - } - catch (InvalidPropertyException ex) { - // Consider as not determinable. - } + public Class getPropertyType(String propertyPath) { return null; } - @Override - public boolean isReadableProperty(String propertyName) { - try { - PropertyHandler ph = getPropertyHandler(propertyName); - if (ph != null) { - return ph.isReadable(); - } - else { - // Maybe an indexed/mapped property... - getPropertyValue(propertyName); - return true; - } - } - catch (InvalidPropertyException ex) { - // Cannot be evaluated, so can't be readable. - } - return false; - } - - @Override - public boolean isWritableProperty(String propertyName) { - try { - PropertyHandler ph = getPropertyHandler(propertyName); - if (ph != null) { - return ph.isWritable(); - } - else { - // Maybe an indexed/mapped property... - getPropertyValue(propertyName); - return true; - } - } - catch (InvalidPropertyException ex) { - // Cannot be evaluated, so can't be writable. - } - return false; - } - - private Object convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class requiredType, - TypeDescriptor td) throws TypeMismatchException { - try { - return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td); - } - catch (ConverterNotFoundException ex) { - PropertyChangeEvent pce = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); - throw new ConversionNotSupportedException(pce, td.getType(), ex); - } - catch (ConversionException ex) { - PropertyChangeEvent pce = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); - throw new TypeMismatchException(pce, requiredType, ex); - } - catch (IllegalStateException ex) { - PropertyChangeEvent pce = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); - throw new ConversionNotSupportedException(pce, requiredType, ex); - } - catch (IllegalArgumentException ex) { - PropertyChangeEvent pce = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); - throw new TypeMismatchException(pce, requiredType, ex); - } - } - - protected Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td) - throws TypeMismatchException { - - return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td); - } - /** * Actually get the value of a property. * @param propertyName name of the property to get the value of @@ -704,459 +139,18 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl * accessor method failed */ @Override - public Object getPropertyValue(String propertyName) throws BeansException { - AbstractPropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); - PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); - return nestedPa.getPropertyValue(tokens); - } - - @SuppressWarnings("unchecked") - protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { - String propertyName = tokens.canonicalName; - String actualName = tokens.actualName; - PropertyHandler ph = getLocalPropertyHandler(actualName); - if (ph == null || !ph.isReadable()) { - throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); - } - try { - Object value = ph.getValue(); - if (tokens.keys != null) { - if (value == null) { - if (isAutoGrowNestedPaths()) { - value = setDefaultValue(tokens.actualName); - } - else { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value of property referenced in indexed " + - "property path '" + propertyName + "': returned null"); - } - } - String indexedPropertyName = tokens.actualName; - // apply indexes and map keys - for (int i = 0; i < tokens.keys.length; i++) { - String key = tokens.keys[i]; - if (value == null) { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value of property referenced in indexed " + - "property path '" + propertyName + "': returned null"); - } - else if (value.getClass().isArray()) { - int index = Integer.parseInt(key); - value = growArrayIfNecessary(value, index, indexedPropertyName); - value = Array.get(value, index); - } - else if (value instanceof List) { - int index = Integer.parseInt(key); - List list = (List) value; - growCollectionIfNecessary(list, index, indexedPropertyName, ph, i + 1); - value = list.get(index); - } - else if (value instanceof Set) { - // Apply index to Iterator in case of a Set. - Set set = (Set) value; - int index = Integer.parseInt(key); - if (index < 0 || index >= set.size()) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Cannot get element with index " + index + " from Set of size " + - set.size() + ", accessed using property path '" + propertyName + "'"); - } - Iterator it = set.iterator(); - for (int j = 0; it.hasNext(); j++) { - Object elem = it.next(); - if (j == index) { - value = elem; - break; - } - } - } - else if (value instanceof Map) { - Map map = (Map) value; - Class mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0); - // IMPORTANT: Do not pass full property name in here - property editors - // must not kick in for map keys but rather only for map values. - TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); - Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); - value = map.get(convertedMapKey); - } - else { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Property referenced in indexed property path '" + propertyName + - "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]"); - } - indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX; - } - } - return value; - } - catch (IndexOutOfBoundsException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Index of out of bounds in property path '" + propertyName + "'", ex); - } - catch (NumberFormatException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Invalid index in property path '" + propertyName + "'", ex); - } - catch (TypeMismatchException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Invalid index in property path '" + propertyName + "'", ex); - } - catch (InvocationTargetException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Getter for property '" + actualName + "' threw exception", ex); - } - catch (Exception ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Illegal attempt to get property '" + actualName + "' threw exception", ex); - } - } - - - /** - * Return the {@link PropertyHandler} for the specified {@code propertyName}, navigating - * if necessary. Return {@code null} if not found rather than throwing an exception. - * @param propertyName the property to obtain the descriptor for - * @return the property descriptor for the specified property, - * or {@code null} if not found - * @throws BeansException in case of introspection failure - */ - protected PropertyHandler getPropertyHandler(String propertyName) throws BeansException { - Assert.notNull(propertyName, "Property name must not be null"); - AbstractPropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); - return nestedPa.getLocalPropertyHandler(getFinalPath(nestedPa, propertyName)); - } - - /** - * Return a {@link PropertyHandler} for the specified local {@code propertyName}. Only - * used to reach a property available in the current context. - * @param propertyName the name of a local property - * @return the handler for that property or {@code null} if it has not been found - */ - protected abstract PropertyHandler getLocalPropertyHandler(String propertyName); - - /** - * Create a new nested property accessor instance. - * Can be overridden in subclasses to create a PropertyAccessor subclass. - * @param object object wrapped by this PropertyAccessor - * @param nestedPath the nested path of the object - * @return the nested PropertyAccessor instance - */ - protected abstract AbstractPropertyAccessor newNestedPropertyAccessor(Object object, String nestedPath); - - /** - * Create a {@link NotWritablePropertyException} for the specified property. - */ - protected abstract NotWritablePropertyException createNotWritablePropertyException(String propertyName); - - - private Object growArrayIfNecessary(Object array, int index, String name) { - if (!isAutoGrowNestedPaths()) { - return array; - } - int length = Array.getLength(array); - if (index >= length && index < this.autoGrowCollectionLimit) { - Class componentType = array.getClass().getComponentType(); - Object newArray = Array.newInstance(componentType, index + 1); - System.arraycopy(array, 0, newArray, 0, length); - for (int i = length; i < Array.getLength(newArray); i++) { - Array.set(newArray, i, newValue(componentType, null, name)); - } - // TODO this is not efficient because conversion may create a copy ... set directly because we know it is assignable. - setPropertyValue(name, newArray); - return getPropertyValue(name); - } - else { - return array; - } - } - - private void growCollectionIfNecessary(Collection collection, int index, String name, - PropertyHandler ph, int nestingLevel) { - - if (!isAutoGrowNestedPaths()) { - return; - } - int size = collection.size(); - if (index >= size && index < this.autoGrowCollectionLimit) { - Class elementType = ph.getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric(); - if (elementType != null) { - for (int i = collection.size(); i < index + 1; i++) { - collection.add(newValue(elementType, null, name)); - } - } - } - } + public abstract Object getPropertyValue(String propertyName) throws BeansException; /** - * Get the last component of the path. Also works if not nested. - * @param pa property accessor to work on - * @param nestedPath property path we know is nested - * @return last component of the path (the property on the target bean) - */ - private String getFinalPath(AbstractPropertyAccessor pa, String nestedPath) { - if (pa == this) { - return nestedPath; - } - return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1); - } - - /** - * Recursively navigate to return a property accessor for the nested property path. - * @param propertyPath property property path, which may be nested - * @return a property accessor for the target bean - */ - @SuppressWarnings("unchecked") // avoid nested generic - protected AbstractPropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) { - int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath); - // Handle nested properties recursively. - if (pos > -1) { - String nestedProperty = propertyPath.substring(0, pos); - String nestedPath = propertyPath.substring(pos + 1); - AbstractPropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty); - return nestedPa.getPropertyAccessorForPropertyPath(nestedPath); - } - else { - return this; - } - } - - /** - * Retrieve a Property accessor for the given nested property. - * Create a new one if not found in the cache. - *

Note: Caching nested PropertyAccessors is necessary now, - * to keep registered custom editors for nested properties. - * @param nestedProperty property to create the PropertyAccessor for - * @return the PropertyAccessor instance, either cached or newly created - */ - private AbstractPropertyAccessor getNestedPropertyAccessor(String nestedProperty) { - if (this.nestedPropertyAccessors == null) { - this.nestedPropertyAccessors = new HashMap(); - } - // Get value of bean property. - PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); - String canonicalName = tokens.canonicalName; - Object value = getPropertyValue(tokens); - if (value == null || (value.getClass().equals(javaUtilOptionalClass) && OptionalUnwrapper.isEmpty(value))) { - if (isAutoGrowNestedPaths()) { - value = setDefaultValue(tokens); - } - else { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName); - } - } - - // Lookup cached sub-PropertyAccessor, create new one if not found. - AbstractPropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName); - if (nestedPa == null || nestedPa.getWrappedInstance() != - (value.getClass().equals(javaUtilOptionalClass) ? OptionalUnwrapper.unwrap(value) : value)) { - if (logger.isTraceEnabled()) { - logger.trace("Creating new nested " + getClass().getSimpleName() + " for property '" + canonicalName + "'"); - } - nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR); - // Inherit all type-specific PropertyEditors. - copyDefaultEditorsTo(nestedPa); - copyCustomEditorsTo(nestedPa, canonicalName); - this.nestedPropertyAccessors.put(canonicalName, nestedPa); - } - else { - if (logger.isTraceEnabled()) { - logger.trace("Using cached nested property accessor for property '" + canonicalName + "'"); - } - } - return nestedPa; - } - - private Object setDefaultValue(String propertyName) { - PropertyTokenHolder tokens = new PropertyTokenHolder(); - tokens.actualName = propertyName; - tokens.canonicalName = propertyName; - return setDefaultValue(tokens); - } - - private Object setDefaultValue(PropertyTokenHolder tokens) { - PropertyValue pv = createDefaultPropertyValue(tokens); - setPropertyValue(tokens, pv); - return getPropertyValue(tokens); - } - - private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) { - TypeDescriptor desc = getPropertyTypeDescriptor(tokens.canonicalName); - Class type = desc.getType(); - if (type == null) { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName, - "Could not determine property type for auto-growing a default value"); - } - Object defaultValue = newValue(type, desc, tokens.canonicalName); - return new PropertyValue(tokens.canonicalName, defaultValue); - } - - private Object newValue(Class type, TypeDescriptor desc, String name) { - try { - if (type.isArray()) { - Class componentType = type.getComponentType(); - // TODO - only handles 2-dimensional arrays - if (componentType.isArray()) { - Object array = Array.newInstance(componentType, 1); - Array.set(array, 0, Array.newInstance(componentType.getComponentType(), 0)); - return array; - } - else { - return Array.newInstance(componentType, 0); - } - } - else if (Collection.class.isAssignableFrom(type)) { - TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null); - return CollectionFactory.createCollection(type, (elementDesc != null ? elementDesc.getType() : null), 16); - } - else if (Map.class.isAssignableFrom(type)) { - TypeDescriptor keyDesc = (desc != null ? desc.getMapKeyTypeDescriptor() : null); - return CollectionFactory.createMap(type, (keyDesc != null ? keyDesc.getType() : null), 16); - } - else { - return BeanUtils.instantiate(type); - } - } - catch (Exception ex) { - // TODO: Root cause exception context is lost here; just exception message preserved. - // Should we throw another exception type that preserves context instead? - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, - "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex); - } - } - - /** - * Parse the given property name into the corresponding property name tokens. - * @param propertyName the property name to parse - * @return representation of the parsed property tokens + * Actually set a property value. + * @param propertyName name of the property to set value of + * @param value the new value + * @throws InvalidPropertyException if there is no such property or + * if the property isn't writable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed or a type mismatch occured */ - private PropertyTokenHolder getPropertyNameTokens(String propertyName) { - PropertyTokenHolder tokens = new PropertyTokenHolder(); - String actualName = null; - List keys = new ArrayList(2); - int searchIndex = 0; - while (searchIndex != -1) { - int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex); - searchIndex = -1; - if (keyStart != -1) { - int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length()); - if (keyEnd != -1) { - if (actualName == null) { - actualName = propertyName.substring(0, keyStart); - } - String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd); - if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { - key = key.substring(1, key.length() - 1); - } - keys.add(key); - searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length(); - } - } - } - tokens.actualName = (actualName != null ? actualName : propertyName); - tokens.canonicalName = tokens.actualName; - if (!keys.isEmpty()) { - tokens.canonicalName += - PROPERTY_KEY_PREFIX + - StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) + - PROPERTY_KEY_SUFFIX; - tokens.keys = StringUtils.toStringArray(keys); - } - return tokens; - } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(getClass().getName()); - if (this.object != null) { - sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]"); - } - else { - sb.append(": no wrapped object set"); - } - return sb.toString(); - } - - - /** - * Handle a given property. - */ - protected abstract static class PropertyHandler { - - private final Class propertyType; - - private final boolean readable; - - private final boolean writable; - - public PropertyHandler(Class propertyType, boolean readable, boolean writable) { - this.propertyType = propertyType; - this.readable = readable; - this.writable = writable; - } - - public Class getPropertyType() { - return this.propertyType; - } - - public boolean isReadable() { - return this.readable; - } - - public boolean isWritable() { - return this.writable; - } - - public abstract TypeDescriptor toTypeDescriptor(); - - public abstract ResolvableType getResolvableType(); - - public Class getMapKeyType(int nestingLevel) { - return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0); - } - - public Class getMapValueType(int nestingLevel) { - return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1); - } - - public Class getCollectionType(int nestingLevel) { - return getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric(); - } - - public abstract TypeDescriptor nested(int level); - - public abstract Object getValue() throws Exception; - - public abstract void setValue(Object object, Object value) throws Exception; - - } - - protected static class PropertyTokenHolder { - - public String canonicalName; - - public String actualName; - - public String[] keys; - } - - - /** - * Inner class to avoid a hard dependency on Java 8. - */ - @UsesJava8 - private static class OptionalUnwrapper { - - public static Object unwrap(Object optionalObject) { - Optional optional = (Optional) optionalObject; - Assert.isTrue(optional.isPresent(), "Optional value must be present"); - Object result = optional.get(); - Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported"); - return result; - } - - public static boolean isEmpty(Object optionalObject) { - return !((Optional) optionalObject).isPresent(); - } - } - + public abstract void setPropertyValue(String propertyName, Object value) throws BeansException; } diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 1d72ff9a9c..637da375bc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -60,7 +60,7 @@ import org.springframework.util.Assert; * @see BeanWrapper * @see PropertyEditorRegistrySupport */ -public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper { +public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper { /** * Cached introspections results for this object, to prevent encountering diff --git a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index 44e43d75f4..687d55d46d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -44,7 +44,7 @@ import org.springframework.util.ReflectionUtils; * @see org.springframework.validation.DirectFieldBindingResult * @see org.springframework.validation.DataBinder#initDirectFieldAccess() */ -public class DirectFieldAccessor extends AbstractPropertyAccessor { +public class DirectFieldAccessor extends AbstractNestablePropertyAccessor { private final Map fieldMap = new HashMap(); diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java similarity index 99% rename from spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java rename to spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java index 7fac2cebeb..0180fbc42d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java @@ -76,7 +76,7 @@ import static org.junit.Assert.*; * @author Dave Syer * @author Stephane Nicoll */ -public abstract class AbstractConfigurablePropertyAccessorTests { +public abstract class AbstractPropertyAccessorTests { @Rule public final ExpectedException thrown = ExpectedException.none(); @@ -1035,7 +1035,7 @@ public abstract class AbstractConfigurablePropertyAccessorTests { @Test public void setPrimitiveArrayPropertyLargeMatching() { Assume.group(TestGroup.PERFORMANCE); - Assume.notLogging(LogFactory.getLog(AbstractConfigurablePropertyAccessorTests.class)); + Assume.notLogging(LogFactory.getLog(AbstractPropertyAccessorTests.class)); PrimitiveArrayBean target = new PrimitiveArrayBean(); AbstractPropertyAccessor accessor = createAccessor(target); diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 24311b0e43..a46275f972 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -36,7 +36,7 @@ import static org.junit.Assert.*; * @author Chris Beams * @author Dave Syer */ -public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessorTests { +public final class BeanWrapperTests extends AbstractPropertyAccessorTests { @Override protected BeanWrapperImpl createAccessor(Object target) { diff --git a/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java index 543f12f406..4145e7bb9b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java @@ -29,7 +29,7 @@ import static org.junit.Assert.*; * @author Chris Beams * @@author Stephane Nicoll */ -public class DirectFieldAccessorTests extends AbstractConfigurablePropertyAccessorTests { +public class DirectFieldAccessorTests extends AbstractPropertyAccessorTests { @Override protected DirectFieldAccessor createAccessor(Object target) {