From 3d86f15a847d4567c3b08b21a21500398a394e31 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 20 Apr 2015 08:39:28 +0200 Subject: [PATCH] Merge BeanWrapperImpl and DirectFieldAccessor `BeanWrapperImpl` and `DirectFieldAccessor` are two `ConfigurablePropertyAccessor` implementations with different features set. This commit harmonizes the two implementations to use a common base class that delegates the actual property handling to the sub-classes: * `BeanWrapperImpl`: `PropertyDescriptor` and introspection utilities * `DirectFieldAccessor`: reflection on `java.lang.Field` Issues: SPR-12206 - SPR-12805 --- ...hodInvocationProceedingJoinPointTests.java | 4 +- .../beans/AbstractPropertyAccessor.java | 1028 ++++++++- .../beans/BeanWrapperImpl.java | 1071 +-------- .../beans/DirectFieldAccessor.java | 282 +-- .../springframework/beans/PropertyValue.java | 8 +- ...ractConfigurablePropertyAccessorTests.java | 1973 +++++++++++++++- .../beans/BeanWrapperTests.java | 2033 +---------------- .../beans/DirectFieldAccessorTests.java | 12 +- .../tests/sample/beans/TestBean.java | 15 +- .../support/WebRequestDataBinderTests.java | 4 +- 10 files changed, 3182 insertions(+), 3248 deletions(-) diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java index 3edb4758bd..c4b1caac4b 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * 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. @@ -211,7 +211,7 @@ public final class MethodInvocationProceedingJoinPointTests { itb.setName("foo"); itb.getDoctor(); itb.getStringArray(); - itb.getSpouses(); + itb.getSpouse(); itb.setSpouse(new TestBean()); try { itb.unreliableFileOperation(); 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 078712179a..c30f89eb06 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-2014 the original author or authors. + * 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. @@ -16,28 +16,163 @@ 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.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 PropertyAccessor} interface. - * Provides base implementations of all convenience methods, with the - * implementation of actual property access left to subclasses. + * 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. * + * @author Rod Johnson * @author Juergen Hoeller + * @author Rob Harrop * @author Stephane Nicoll * @since 2.0 - * @see #getPropertyValue + * @see #registerCustomEditor + * @see #setPropertyValues * @see #setPropertyValue + * @see #getPropertyValue + * @see #getPropertyType + * @see BeanWrapper + * @see PropertyEditorRegistrySupport */ 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) { @@ -59,10 +194,127 @@ 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 { - setPropertyValue(pv.getName(), pv.getValue()); + 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); + } } @Override @@ -122,13 +374,314 @@ 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()) { + oldValue = ph.getValue(); + } + 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 Class getPropertyType(String propertyPath) { + 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. + } 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 @@ -139,18 +692,459 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl * accessor method failed */ @Override - public abstract Object getPropertyValue(String propertyName) throws BeansException; + 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); + } + } + /** - * 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 + * 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)); + } + } + } + } + + /** + * 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 + */ + 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 abstract void setPropertyValue(String propertyName, Object value) throws BeansException; + 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/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 704e9998e3..1d72ff9a9c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -16,41 +16,19 @@ package org.springframework.beans; -import java.beans.PropertyChangeEvent; import java.beans.PropertyDescriptor; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.UndeclaredThrowableException; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -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.GenericCollectionTypeResolver; -import org.springframework.core.convert.ConversionException; -import org.springframework.core.convert.ConverterNotFoundException; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.Property; 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; /** * Default {@link BeanWrapper} implementation that should be sufficient @@ -64,13 +42,6 @@ import org.springframework.util.StringUtils; * across the application). See the base class * {@link PropertyEditorRegistrySupport} for details. * - *

{@code BeanWrapperImpl} 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. - * *

NOTE: As of Spring 2.5, this is - for almost all purposes - an * internal class. It is just public in order to allow for access from * other framework packages. For standard application access purposes, use the @@ -91,36 +62,6 @@ import org.springframework.util.StringUtils; */ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper { - /** - * 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(BeanWrapperImpl.class); - - private static Class javaUtilOptionalClass = null; - - static { - try { - javaUtilOptionalClass = - ClassUtils.forName("java.util.Optional", BeanWrapperImpl.class.getClassLoader()); - } - catch (ClassNotFoundException ex) { - // Java 8 not available - Optional references simply not supported then. - } - } - - - /** The wrapped object */ - private Object object; - - private String nestedPath = ""; - - private Object rootObject; - - /** - * The security context used for invoking the property methods - */ - private AccessControlContext acc; - /** * Cached introspections results for this object, to prevent encountering * the cost of JavaBeans introspection every time. @@ -128,12 +69,9 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra private CachedIntrospectionResults cachedIntrospectionResults; /** - * Map with cached nested BeanWrappers: nested path -> BeanWrapper instance. + * The security context used for invoking the property methods */ - private Map nestedBeanWrappers; - - private int autoGrowCollectionLimit = Integer.MAX_VALUE; - + private AccessControlContext acc; /** * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. @@ -151,10 +89,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra * @see #setWrappedInstance */ public BeanWrapperImpl(boolean registerDefaultEditors) { - if (registerDefaultEditors) { - registerDefaultEditors(); - } - this.typeConverterDelegate = new TypeConverterDelegate(this); + super(registerDefaultEditors); } /** @@ -162,8 +97,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra * @param object object wrapped by this BeanWrapper */ public BeanWrapperImpl(Object object) { - registerDefaultEditors(); - setWrappedInstance(object); + super(object); } /** @@ -171,8 +105,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra * @param clazz class to instantiate and wrap */ public BeanWrapperImpl(Class clazz) { - registerDefaultEditors(); - setWrappedInstance(BeanUtils.instantiateClass(clazz)); + super(clazz); } /** @@ -183,8 +116,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra * @param rootObject the root object at the top of the path */ public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) { - registerDefaultEditors(); - setWrappedInstance(object, nestedPath, rootObject); + super(object, nestedPath, rootObject); } /** @@ -195,100 +127,14 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra * @param superBw the containing BeanWrapper (must not be {@code null}) */ private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) { - setWrappedInstance(object, nestedPath, superBw.getWrappedInstance()); - setExtractOldValueForEditor(superBw.isExtractOldValueForEditor()); - setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths()); - setAutoGrowCollectionLimit(superBw.getAutoGrowCollectionLimit()); - setConversionService(superBw.getConversionService()); + super(object, nestedPath, superBw); setSecurityContext(superBw.acc); } - - //--------------------------------------------------------------------- - // Implementation of BeanWrapper interface - //--------------------------------------------------------------------- - - /** - * 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.nestedBeanWrappers = null; - this.typeConverterDelegate = new TypeConverterDelegate(this, this.object); - setIntrospectionClass(this.object.getClass()); - } - - @Override - public final Object getWrappedInstance() { - return this.object; - } - @Override - public final Class getWrappedClass() { - return (this.object != null ? this.object.getClass() : null); - } - - /** - * Return the nested path of the object wrapped by this BeanWrapper. - */ - public final String getNestedPath() { - return this.nestedPath; - } - - /** - * Return the root object at the top of the path of this BeanWrapper. - * @see #getNestedPath - */ - public final Object getRootInstance() { - return this.rootObject; - } - - /** - * Return the class of the root object at the top of the path of this BeanWrapper. - * @see #getNestedPath - */ - public final Class getRootClass() { - return (this.rootObject != null ? this.rootObject.getClass() : null); - } - - - - /** - * Specify a limit for array and collection auto-growing. - *

Default is unlimited on a plain BeanWrapper. - */ - @Override - public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) { - this.autoGrowCollectionLimit = autoGrowCollectionLimit; - } - - /** - * Return the limit for array and collection auto-growing. - */ - @Override - public int getAutoGrowCollectionLimit() { - return this.autoGrowCollectionLimit; + public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { + super.setWrappedInstance(object, nestedPath, rootObject); + setIntrospectionClass(getWrappedInstance().getClass()); } /** @@ -324,165 +170,13 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra * for the wrapped object. */ private CachedIntrospectionResults getCachedIntrospectionResults() { - Assert.state(this.object != null, "BeanWrapper does not hold a bean instance"); + Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance"); if (this.cachedIntrospectionResults == null) { this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass()); } return this.cachedIntrospectionResults; } - - @Override - public PropertyDescriptor[] getPropertyDescriptors() { - return getCachedIntrospectionResults().getPropertyDescriptors(); - } - - @Override - public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException { - PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); - if (pd == null) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "No property '" + propertyName + "' found"); - } - return pd; - } - - /** - * Internal version of {@link #getPropertyDescriptor}: - * Returns {@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 PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException { - Assert.notNull(propertyName, "Property name must not be null"); - BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); - return nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(getFinalPath(nestedBw, propertyName)); - } - - @Override - public Class getPropertyType(String propertyName) throws BeansException { - try { - PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); - if (pd != null) { - return pd.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 { - BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); - String finalPath = getFinalPath(nestedBw, propertyName); - PropertyTokenHolder tokens = getPropertyNameTokens(finalPath); - PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(tokens.actualName); - if (pd != null) { - if (tokens.keys != null) { - if (pd.getReadMethod() != null || pd.getWriteMethod() != null) { - return TypeDescriptor.nested(property(pd), tokens.keys.length); - } - } - else { - if (pd.getReadMethod() != null || pd.getWriteMethod() != null) { - return new TypeDescriptor(property(pd)); - } - } - } - } - catch (InvalidPropertyException ex) { - // Consider as not determinable. - } - return null; - } - - @Override - public boolean isReadableProperty(String propertyName) { - try { - PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); - if (pd != null) { - if (pd.getReadMethod() != null) { - return true; - } - } - 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 { - PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); - if (pd != null) { - if (pd.getWriteMethod() != null) { - return true; - } - } - 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); - } - } - /** * Convert the given value for the specified property to the latter's type. *

This method is only intended for optimizations in a BeanFactory. @@ -497,7 +191,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults(); PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName); if (pd == null) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, "No property '" + propertyName + "' found"); } TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); @@ -507,232 +201,78 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra return convertForProperty(propertyName, null, value, td); } - private Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td) - throws TypeMismatchException { - - return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td); - } - private Property property(PropertyDescriptor pd) { GenericTypeAwarePropertyDescriptor typeAware = (GenericTypeAwarePropertyDescriptor) pd; return new Property(typeAware.getBeanClass(), typeAware.getReadMethod(), typeAware.getWriteMethod(), typeAware.getName()); } - //--------------------------------------------------------------------- - // Implementation methods - //--------------------------------------------------------------------- - - /** - * Get the last component of the path. Also works if not nested. - * @param bw BeanWrapper 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(BeanWrapper bw, String nestedPath) { - if (bw == this) { - return nestedPath; - } - return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1); - } - - /** - * Recursively navigate to return a BeanWrapper for the nested property path. - * @param propertyPath property property path, which may be nested - * @return a BeanWrapper for the target bean - */ - protected BeanWrapperImpl getBeanWrapperForPropertyPath(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); - BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty); - return nestedBw.getBeanWrapperForPropertyPath(nestedPath); - } - else { - return this; + @Override + protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) { + PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); + if (pd != null) { + return new BeanPropertyHandler(pd); } + return null; } - /** - * Retrieve a BeanWrapper for the given nested property. - * Create a new one if not found in the cache. - *

Note: Caching nested BeanWrappers is necessary now, - * to keep registered custom editors for nested properties. - * @param nestedProperty property to create the BeanWrapper for - * @return the BeanWrapper instance, either cached or newly created - */ - private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) { - if (this.nestedBeanWrappers == null) { - this.nestedBeanWrappers = 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-BeanWrapper, create new one if not found. - BeanWrapperImpl nestedBw = this.nestedBeanWrappers.get(canonicalName); - if (nestedBw == null || nestedBw.getWrappedInstance() != - (value.getClass().equals(javaUtilOptionalClass) ? OptionalUnwrapper.unwrap(value) : value)) { - if (logger.isTraceEnabled()) { - logger.trace("Creating new nested BeanWrapper for property '" + canonicalName + "'"); - } - nestedBw = newNestedBeanWrapper(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR); - // Inherit all type-specific PropertyEditors. - copyDefaultEditorsTo(nestedBw); - copyCustomEditorsTo(nestedBw, canonicalName); - this.nestedBeanWrappers.put(canonicalName, nestedBw); - } - else { - if (logger.isTraceEnabled()) { - logger.trace("Using cached nested BeanWrapper for property '" + canonicalName + "'"); - } - } - return nestedBw; + @Override + protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) { + return new BeanWrapperImpl(object, nestedPath, this); } - private Object setDefaultValue(String propertyName) { - PropertyTokenHolder tokens = new PropertyTokenHolder(); - tokens.actualName = propertyName; - tokens.canonicalName = propertyName; - return setDefaultValue(tokens); + @Override + protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) { + PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); + throw new NotWritablePropertyException( + getRootClass(), getNestedPath() + propertyName, + matches.buildErrorMessage(), matches.getPossibleMatches()); } - private Object setDefaultValue(PropertyTokenHolder tokens) { - PropertyValue pv = createDefaultPropertyValue(tokens); - setPropertyValue(tokens, pv); - return getPropertyValue(tokens); + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return getCachedIntrospectionResults().getPropertyDescriptors(); } - 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"); + @Override + public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException { + BeanPropertyHandler propertyHandler = getLocalPropertyHandler(propertyName); + if (propertyHandler == null) { + throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, + "No property '" + propertyName + "' found"); } - Object defaultValue = newValue(type, desc, tokens.canonicalName); - return new PropertyValue(tokens.canonicalName, defaultValue); + return propertyHandler.pd; } - 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); - } - } - /** - * Create a new nested BeanWrapper instance. - *

Default implementation creates a BeanWrapperImpl instance. - * Can be overridden in subclasses to create a BeanWrapperImpl subclass. - * @param object object wrapped by this BeanWrapper - * @param nestedPath the nested path of the object - * @return the nested BeanWrapper instance - */ - protected BeanWrapperImpl newNestedBeanWrapper(Object object, String nestedPath) { - return new BeanWrapperImpl(object, nestedPath, this); - } + private class BeanPropertyHandler extends PropertyHandler { - /** - * 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; - } + private final PropertyDescriptor pd; + public BeanPropertyHandler(PropertyDescriptor pd) { + super(pd.getPropertyType(), + pd.getReadMethod() != null, pd.getWriteMethod() != null); + this.pd = pd; + } - //--------------------------------------------------------------------- - // Implementation of PropertyAccessor interface - //--------------------------------------------------------------------- + @Override + public ResolvableType getResolvableType() { + return ResolvableType.forMethodReturnType(this.pd.getReadMethod()); + } - @Override - public Object getPropertyValue(String propertyName) throws BeansException { - BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); - PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); - return nestedBw.getPropertyValue(tokens); - } + @Override + public TypeDescriptor toTypeDescriptor() { + return new TypeDescriptor(property(this.pd)); + } - @SuppressWarnings("unchecked") - private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { - String propertyName = tokens.canonicalName; - String actualName = tokens.actualName; - PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - if (pd == null || pd.getReadMethod() == null) { - throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); + @Override + public TypeDescriptor nested(int level) { + return TypeDescriptor.nested(property(pd), level); } - final Method readMethod = pd.getReadMethod(); - try { + + @Override + public Object getValue() throws Exception { + final Method readMethod = this.pd.getReadMethod(); if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && !readMethod.isAccessible()) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction() { @@ -748,13 +288,12 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } - Object value; if (System.getSecurityManager() != null) { try { - value = AccessController.doPrivileged(new PrivilegedExceptionAction() { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public Object run() throws Exception { - return readMethod.invoke(object, (Object[]) null); + return readMethod.invoke(getWrappedInstance(), (Object[]) null); } }, acc); } @@ -763,479 +302,47 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } else { - value = readMethod.invoke(object, (Object[]) null); + return readMethod.invoke(getWrappedInstance(), (Object[]) null); } - - 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, pd, 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 = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1); - // 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); - } - } - - 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, - PropertyDescriptor pd, int nestingLevel) { - if (!isAutoGrowNestedPaths()) { - return; - } - int size = collection.size(); - if (index >= size && index < this.autoGrowCollectionLimit) { - Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel); - if (elementType != null) { - for (int i = collection.size(); i < index + 1; i++) { - collection.add(newValue(elementType, null, name)); - } - } - } - } - - @Override - public void setPropertyValue(String propertyName, Object value) throws BeansException { - BeanWrapperImpl nestedBw; - try { - nestedBw = getBeanWrapperForPropertyPath(propertyName); - } - catch (NotReadablePropertyException ex) { - throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, - "Nested property in path '" + propertyName + "' does not exist", ex); - } - PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); - nestedBw.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(); - BeanWrapperImpl nestedBw; - try { - nestedBw = getBeanWrapperForPropertyPath(propertyName); - } - catch (NotReadablePropertyException ex) { - throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, - "Nested property in path '" + propertyName + "' does not exist", ex); - } - tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); - if (nestedBw == this) { - pv.getOriginalPropertyValue().resolvedTokens = tokens; - } - nestedBw.setPropertyValue(tokens, pv); - } - else { - setPropertyValue(tokens, pv); - } - } - - @SuppressWarnings("unchecked") - private 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); + @Override + public void setValue(final Object object, Object valueToApply) throws Exception { + final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ? + ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() : + this.pd.getWriteMethod()); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + writeMethod.setAccessible(true); + return null; + } + }); } else { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value in property referenced " + - "in indexed property path '" + propertyName + "': returned null"); + writeMethod.setAccessible(true); } } - if (propValue.getClass().isArray()) { - PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - Class requiredType = propValue.getClass().getComponentType(); - int arrayIndex = Integer.parseInt(key); - Object oldValue = null; + final Object value = valueToApply; + if (System.getSecurityManager() != null) { try { - if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { - oldValue = Array.get(propValue, arrayIndex); - } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - requiredType, TypeDescriptor.nested(property(pd), 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) { - PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType( - pd.getReadMethod(), 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, TypeDescriptor.nested(property(pd), 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"); + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + writeMethod.invoke(object, value); + return null; } - } - 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); - } + }, acc); } - } - else if (propValue instanceof Map) { - PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( - pd.getReadMethod(), tokens.keys.length); - Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( - pd.getReadMethod(), 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); + catch (PrivilegedActionException ex) { + throw ex.getException(); } - // 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, TypeDescriptor.nested(property(pd), 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 { - PropertyDescriptor pd = pv.resolvedDescriptor; - if (pd == null || !pd.getWriteMethod().getDeclaringClass().isInstance(this.object)) { - pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - if (pd == null || pd.getWriteMethod() == null) { - if (pv.isOptional()) { - logger.debug("Ignoring optional value for property '" + actualName + - "' - property not found on bean class [" + getRootClass().getName() + "]"); - return; - } - else { - PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); - throw new NotWritablePropertyException( - getRootClass(), this.nestedPath + propertyName, - matches.buildErrorMessage(), matches.getPossibleMatches()); - } - } - pv.getOriginalPropertyValue().resolvedDescriptor = pd; + writeMethod.invoke(getWrappedInstance(), value); } - - 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() && pd.getReadMethod() != null) { - final Method readMethod = pd.getReadMethod(); - if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && - !readMethod.isAccessible()) { - if (System.getSecurityManager()!= null) { - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - readMethod.setAccessible(true); - return null; - } - }); - } - else { - readMethod.setAccessible(true); - } - } - try { - if (System.getSecurityManager() != null) { - oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws Exception { - return readMethod.invoke(object); - } - }, acc); - } - else { - oldValue = readMethod.invoke(object); - } - } - 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, new TypeDescriptor(property(pd))); - } - pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); - } - final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ? - ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() : - pd.getWriteMethod()); - if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { - if (System.getSecurityManager()!= null) { - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - writeMethod.setAccessible(true); - return null; - } - }); - } - else { - writeMethod.setAccessible(true); - } - } - final Object value = valueToApply; - if (System.getSecurityManager() != null) { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws Exception { - writeMethod.invoke(object, value); - return null; - } - }, acc); - } - catch (PrivilegedActionException ex) { - throw ex.getException(); - } - } - else { - writeMethod.invoke(this.object, value); - } - } - 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, pd.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 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(); - } - - - private 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/DirectFieldAccessor.java b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index c9d188cdf3..2656cc50db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * 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. @@ -16,25 +16,22 @@ package org.springframework.beans; -import java.beans.PropertyChangeEvent; import java.lang.reflect.Field; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.StringTokenizer; -import org.springframework.core.convert.ConversionException; -import org.springframework.core.convert.ConverterNotFoundException; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; -import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** - * {@link PropertyAccessor} implementation that directly accesses instance fields. - * Allows for direct binding to fields instead of going through JavaBean setters. + * {@link ConfigurablePropertyAccessor} implementation that directly accesses + * instance fields. Allows for direct binding to fields instead of going through + * JavaBean setters. * - *

As of Spring 4.1, this implementation supports nested field traversal. + *

As of Spring 4.2, the vast majority of the {@link BeanWrapper} features have + * been merged to {@link AbstractPropertyAccessor}, which means that property + * traversal as well as collections and map access is now supported here as well. * *

A DirectFieldAccessor's default for the "extractOldValueForEditor" setting * is "true", since a field can always be read without side effects. @@ -49,264 +46,89 @@ import org.springframework.util.ReflectionUtils; */ public class DirectFieldAccessor extends AbstractPropertyAccessor { - private final Object rootObject; + private final Map fieldMap = new HashMap(); - private final Map fieldMap = new HashMap(); - - - /** - * Create a new DirectFieldAccessor for the given root object. - * @param rootObject the root object to access - */ - public DirectFieldAccessor(final Object rootObject) { - Assert.notNull(rootObject, "Root object must not be null"); - this.rootObject = rootObject; - this.typeConverterDelegate = new TypeConverterDelegate(this, rootObject); - registerDefaultEditors(); - setExtractOldValueForEditor(true); - } - - /** - * Return the root object at the top of the path of this instance. - */ - public final Object getRootInstance() { - return this.rootObject; - } - - /** - * Return the class of the root object at the top of the path of this instance. - */ - public final Class getRootClass() { - return (this.rootObject != null ? this.rootObject.getClass() : null); - } - - @Override - public boolean isReadableProperty(String propertyName) throws BeansException { - return hasProperty(propertyName); + public DirectFieldAccessor(Object object) { + super(object); } - @Override - public boolean isWritableProperty(String propertyName) throws BeansException { - return hasProperty(propertyName); - } - - @Override - public Class getPropertyType(String propertyPath) throws BeansException { - FieldAccessor fieldAccessor = getFieldAccessor(propertyPath); - if (fieldAccessor != null) { - return fieldAccessor.getField().getType(); - } - return null; + protected DirectFieldAccessor(Object object, String nestedPath, DirectFieldAccessor superBw) { + super(object, nestedPath, superBw); } @Override - public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { - FieldAccessor fieldAccessor = getFieldAccessor(propertyName); - if (fieldAccessor != null) { - return new TypeDescriptor(fieldAccessor.getField()); + protected FieldPropertyHandler getLocalPropertyHandler(String propertyName) { + FieldPropertyHandler propertyHandler = this.fieldMap.get(propertyName); + if (propertyHandler == null) { + Field field = ReflectionUtils.findField(getWrappedClass(), propertyName); + if (field != null) { + propertyHandler = new FieldPropertyHandler(field); + } + this.fieldMap.put(propertyName, propertyHandler); } - return null; + return propertyHandler; } @Override - public Object getPropertyValue(String propertyName) throws BeansException { - FieldAccessor fieldAccessor = getFieldAccessor(propertyName); - if (fieldAccessor == null) { - throw new NotReadablePropertyException( - getRootClass(), propertyName, "Field '" + propertyName + "' does not exist"); - } - return fieldAccessor.getValue(); + protected DirectFieldAccessor newNestedPropertyAccessor(Object object, String nestedPath) { + return new DirectFieldAccessor(object, nestedPath, this); } @Override - public void setPropertyValue(String propertyName, Object newValue) throws BeansException { - FieldAccessor fieldAccessor = getFieldAccessor(propertyName); - if (fieldAccessor == null) { - throw new NotWritablePropertyException( - getRootClass(), propertyName, "Field '" + propertyName + "' does not exist"); - } - Field field = fieldAccessor.getField(); - Object oldValue = null; - try { - oldValue = fieldAccessor.getValue(); - Object convertedValue = this.typeConverterDelegate.convertIfNecessary( - field.getName(), oldValue, newValue, field.getType(), new TypeDescriptor(field)); - fieldAccessor.setValue(convertedValue); - } - catch (ConverterNotFoundException ex) { - PropertyChangeEvent pce = new PropertyChangeEvent(getRootInstance(), propertyName, oldValue, newValue); - throw new ConversionNotSupportedException(pce, field.getType(), ex); - } - catch (ConversionException ex) { - PropertyChangeEvent pce = new PropertyChangeEvent(getRootInstance(), propertyName, oldValue, newValue); - throw new TypeMismatchException(pce, field.getType(), ex); - } - catch (IllegalStateException ex) { - PropertyChangeEvent pce = new PropertyChangeEvent(getRootInstance(), propertyName, oldValue, newValue); - throw new ConversionNotSupportedException(pce, field.getType(), ex); - } - catch (IllegalArgumentException ex) { - PropertyChangeEvent pce = new PropertyChangeEvent(getRootInstance(), propertyName, oldValue, newValue); - throw new TypeMismatchException(pce, field.getType(), ex); - } + protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) { + throw new NotWritablePropertyException( + getRootClass(), getNestedPath() + propertyName, "Field does not exist", + new String[0]); } - private boolean hasProperty(String propertyPath) { - Assert.notNull(propertyPath, "PropertyPath must not be null"); - return getFieldAccessor(propertyPath) != null; - } - private FieldAccessor getFieldAccessor(String propertyPath) { - FieldAccessor fieldAccessor = this.fieldMap.get(propertyPath); - if (fieldAccessor == null) { - fieldAccessor = doGetFieldAccessor(propertyPath, getRootClass()); - this.fieldMap.put(propertyPath, fieldAccessor); - } - return fieldAccessor; - } - - private FieldAccessor doGetFieldAccessor(String propertyPath, Class targetClass) { - StringTokenizer st = new StringTokenizer(propertyPath, "."); - FieldAccessor accessor = null; - Class parentType = targetClass; - while (st.hasMoreTokens()) { - String localProperty = st.nextToken(); - Field field = ReflectionUtils.findField(parentType, localProperty); - if (field == null) { - return null; - } - if (accessor == null) { - accessor = root(propertyPath, localProperty, field); - } - else { - accessor = accessor.child(localProperty, field); - } - parentType = field.getType(); - } - return accessor; - } - - /** - * Create a root {@link FieldAccessor}. - * @param canonicalName the full expression for the field to access - * @param actualName the name of the local (root) property - * @param field the field accessing the property - */ - private FieldAccessor root(String canonicalName, String actualName, Field field) { - return new FieldAccessor(null, canonicalName, actualName, field); - } - - - /** - * Provide an easy access to a potentially hierarchical value. - */ - private class FieldAccessor { - - private final List parents; - - private final String canonicalName; - - private final String actualName; + private class FieldPropertyHandler extends PropertyHandler { private final Field field; - /** - * Create a new FieldAccessor instance. - * @param parent the parent accessor, if any - * @param canonicalName the full expression for the field to access - * @param actualName the name of the partial expression for this property - * @param field the field accessing the property - */ - public FieldAccessor(FieldAccessor parent, String canonicalName, String actualName, Field field) { - Assert.notNull(canonicalName, "Expression must no be null"); - Assert.notNull(field, "Field must no be null"); - this.parents = buildParents(parent); - this.canonicalName = canonicalName; - this.actualName = actualName; + public FieldPropertyHandler(Field field) { + super(field.getType(), true, true); this.field = field; } - /** - * Create a child instance. - * @param actualName the name of the child property - * @param field the field accessing the child property - */ - public FieldAccessor child(String actualName, Field field) { - return new FieldAccessor(this, this.canonicalName, this.actualName + "." + actualName, field); + @Override + public TypeDescriptor toTypeDescriptor() { + return new TypeDescriptor(this.field); } - public Field getField() { - return this.field; + @Override + public ResolvableType getResolvableType() { + return ResolvableType.forField(this.field); } - public Object getValue() { - Object localTarget = getLocalTarget(getRootInstance()); - return getParentValue(localTarget); - + @Override + public TypeDescriptor nested(int level) { + return TypeDescriptor.nested(this.field, level); } - public void setValue(Object value) { - Object localTarget = getLocalTarget(getRootInstance()); + @Override + public Object getValue() throws Exception { try { - this.field.set(localTarget, value); + ReflectionUtils.makeAccessible(this.field); + return this.field.get(getWrappedInstance()); } + catch (IllegalAccessException ex) { - throw new InvalidPropertyException(localTarget.getClass(), canonicalName, "Field is not accessible", ex); + throw new InvalidPropertyException(getWrappedClass(), + this.field.getName(), "Field is not accessible", ex); } } - private Object getParentValue(Object target) { + @Override + public void setValue(Object object, Object value) throws Exception { try { ReflectionUtils.makeAccessible(this.field); - return this.field.get(target); + this.field.set(object, value); } catch (IllegalAccessException ex) { - throw new InvalidPropertyException(target.getClass(), - this.canonicalName, "Field is not accessible", ex); - } - } - - private Object getLocalTarget(Object rootTarget) { - Object localTarget = rootTarget; - for (FieldAccessor parent : parents) { - localTarget = autoGrowIfNecessary(parent, parent.getParentValue(localTarget)); - if (localTarget == null) { // Could not traverse the graph any further - throw new NullValueInNestedPathException(getRootClass(), parent.actualName, - "Cannot access indexed value of property referenced in indexed property path '" + - getField().getName() + "': returned null"); - } - } - return localTarget; - } - - private Object newValue() { - Class type = getField().getType(); - try { - return type.newInstance(); - } - catch (Exception ex) { - throw new NullValueInNestedPathException(getRootClass(), this.actualName, - "Could not instantiate property type [" + type.getName() + - "] to auto-grow nested property path: " + ex); - } - } - - private Object autoGrowIfNecessary(FieldAccessor accessor, Object value) { - if (value == null && isAutoGrowNestedPaths()) { - Object defaultValue = accessor.newValue(); - accessor.setValue(defaultValue); - return defaultValue; - } - return value; - } - - private List buildParents(FieldAccessor parent) { - List parents = new ArrayList(); - if (parent != null) { - parents.addAll(parent.parents); - parents.add(parent); + throw new InvalidPropertyException(getWrappedClass(), this.field.getName(), + "Field is not accessible", ex); } - return parents; } } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java b/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java index b2058a2ae1..4f4f37ca79 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * 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. @@ -16,7 +16,6 @@ package org.springframework.beans; -import java.beans.PropertyDescriptor; import java.io.Serializable; import org.springframework.util.Assert; @@ -60,9 +59,6 @@ public class PropertyValue extends BeanMetadataAttributeAccessor implements Seri /** Package-visible field for caching the resolved property path tokens */ transient volatile Object resolvedTokens; - /** Package-visible field for caching the resolved PropertyDescriptor */ - transient volatile PropertyDescriptor resolvedDescriptor; - /** * Create a new PropertyValue instance. @@ -88,7 +84,6 @@ public class PropertyValue extends BeanMetadataAttributeAccessor implements Seri this.convertedValue = original.convertedValue; this.conversionNecessary = original.conversionNecessary; this.resolvedTokens = original.resolvedTokens; - this.resolvedDescriptor = original.resolvedDescriptor; copyAttributesFrom(original); } @@ -106,7 +101,6 @@ public class PropertyValue extends BeanMetadataAttributeAccessor implements Seri this.optional = original.isOptional(); this.conversionNecessary = original.conversionNecessary; this.resolvedTokens = original.resolvedTokens; - this.resolvedDescriptor = original.resolvedDescriptor; copyAttributesFrom(original); } diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java index e5d79830b3..6dc6e390dc 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractConfigurablePropertyAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * 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. @@ -16,15 +16,64 @@ package org.springframework.beans; +import java.beans.PropertyEditorSupport; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.apache.commons.logging.LogFactory; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static org.hamcrest.CoreMatchers.*; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.propertyeditors.CustomNumberEditor; +import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.beans.support.DerivedFromProtectedBaseBean; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; +import org.springframework.tests.sample.beans.BooleanTestBean; +import org.springframework.tests.sample.beans.ITestBean; +import org.springframework.tests.sample.beans.IndexedTestBean; +import org.springframework.tests.sample.beans.NumberTestBean; +import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.StopWatch; +import org.springframework.util.StringUtils; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.*; /** + * Shared tests for property accessors. * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Alef Arendsen + * @author Arjen Poutsma + * @author Chris Beams + * @author Dave Syer * @author Stephane Nicoll */ public abstract class AbstractConfigurablePropertyAccessorTests { @@ -33,18 +82,30 @@ public abstract class AbstractConfigurablePropertyAccessorTests { public final ExpectedException thrown = ExpectedException.none(); - protected abstract ConfigurablePropertyAccessor createAccessor(Object target); + protected abstract AbstractPropertyAccessor createAccessor(Object target); + + + @Test + public void createWithNullTarget() { + try { + createAccessor(null); + fail("Must throw an exception when constructed with null object"); + } + catch (IllegalArgumentException ex) { + // expected + } + } @Test public void isReadableProperty() { - ConfigurablePropertyAccessor accessor = createAccessor(new Simple("John", 2)); + AbstractPropertyAccessor accessor = createAccessor(new Simple("John", 2)); assertThat(accessor.isReadableProperty("name"), is(true)); } @Test public void isReadablePropertyNotReadable() { - ConfigurablePropertyAccessor accessor = createAccessor(new NoRead()); + AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertFalse(accessor.isReadableProperty("age")); } @@ -54,14 +115,14 @@ public abstract class AbstractConfigurablePropertyAccessorTests { */ @Test public void isReadablePropertyNoSuchProperty() { - ConfigurablePropertyAccessor accessor = createAccessor(new NoRead()); + AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertFalse(accessor.isReadableProperty("xxxxx")); } @Test public void isReadablePropertyNull() { - ConfigurablePropertyAccessor accessor = createAccessor(new NoRead()); + AbstractPropertyAccessor accessor = createAccessor(new NoRead()); thrown.expect(IllegalArgumentException.class); accessor.isReadableProperty(null); @@ -69,14 +130,14 @@ public abstract class AbstractConfigurablePropertyAccessorTests { @Test public void isWritableProperty() { - ConfigurablePropertyAccessor accessor = createAccessor(new Simple("John", 2)); + AbstractPropertyAccessor accessor = createAccessor(new Simple("John", 2)); assertThat(accessor.isWritableProperty("name"), is(true)); } @Test public void isWritablePropertyNull() { - ConfigurablePropertyAccessor accessor = createAccessor(new NoRead()); + AbstractPropertyAccessor accessor = createAccessor(new NoRead()); thrown.expect(IllegalArgumentException.class); accessor.isWritableProperty(null); @@ -84,38 +145,99 @@ public abstract class AbstractConfigurablePropertyAccessorTests { @Test public void isWritablePropertyNoSuchProperty() { - ConfigurablePropertyAccessor accessor = createAccessor(new NoRead()); + AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertFalse(accessor.isWritableProperty("xxxxx")); } + @Test + public void isReadableWritableForIndexedProperties() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + + assertTrue(accessor.isReadableProperty("array")); + assertTrue(accessor.isReadableProperty("list")); + assertTrue(accessor.isReadableProperty("set")); + assertTrue(accessor.isReadableProperty("map")); + assertFalse(accessor.isReadableProperty("xxx")); + + assertTrue(accessor.isWritableProperty("array")); + assertTrue(accessor.isWritableProperty("list")); + assertTrue(accessor.isWritableProperty("set")); + assertTrue(accessor.isWritableProperty("map")); + assertFalse(accessor.isWritableProperty("xxx")); + + assertTrue(accessor.isReadableProperty("array[0]")); + assertTrue(accessor.isReadableProperty("array[0].name")); + assertTrue(accessor.isReadableProperty("list[0]")); + assertTrue(accessor.isReadableProperty("list[0].name")); + assertTrue(accessor.isReadableProperty("set[0]")); + assertTrue(accessor.isReadableProperty("set[0].name")); + assertTrue(accessor.isReadableProperty("map[key1]")); + assertTrue(accessor.isReadableProperty("map[key1].name")); + assertTrue(accessor.isReadableProperty("map[key4][0]")); + assertTrue(accessor.isReadableProperty("map[key4][0].name")); + assertTrue(accessor.isReadableProperty("map[key4][1]")); + assertTrue(accessor.isReadableProperty("map[key4][1].name")); + assertFalse(accessor.isReadableProperty("array[key1]")); + + assertTrue(accessor.isWritableProperty("array[0]")); + assertTrue(accessor.isWritableProperty("array[0].name")); + assertTrue(accessor.isWritableProperty("list[0]")); + assertTrue(accessor.isWritableProperty("list[0].name")); + assertTrue(accessor.isWritableProperty("set[0]")); + assertTrue(accessor.isWritableProperty("set[0].name")); + assertTrue(accessor.isWritableProperty("map[key1]")); + assertTrue(accessor.isWritableProperty("map[key1].name")); + assertTrue(accessor.isWritableProperty("map[key4][0]")); + assertTrue(accessor.isWritableProperty("map[key4][0].name")); + assertTrue(accessor.isWritableProperty("map[key4][1]")); + assertTrue(accessor.isWritableProperty("map[key4][1].name")); + assertFalse(accessor.isWritableProperty("array[key1]")); + } + @Test public void getSimpleProperty() { - Simple simple = new Simple("John", 2); - ConfigurablePropertyAccessor accessor = createAccessor(simple); + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyValue("name"), is("John")); } @Test public void getNestedProperty() { - Person person = createPerson("John", "London", "UK"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + Person target = createPerson("John", "London", "UK"); + AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyValue("address.city"), is("London")); } @Test public void getNestedDeepProperty() { - Person person = createPerson("John", "London", "UK"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + Person target = createPerson("John", "London", "UK"); + AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyValue("address.country.name"), is("UK")); } @Test - public void getPropertyIntermediateFieldIsNull() { - Person person = createPerson("John", "London", "UK"); - person.address = null; - ConfigurablePropertyAccessor accessor = createAccessor(person); + public void getAnotherNestedDeepProperty() { + ITestBean target = new TestBean("rod", 31); + ITestBean kerry = new TestBean("kerry", 35); + target.setSpouse(kerry); + kerry.setSpouse(target); + AbstractPropertyAccessor accessor = createAccessor(target); + Integer KA = (Integer) accessor.getPropertyValue("spouse.age"); + assertTrue("kerry is 35", KA == 35); + Integer RA = (Integer) accessor.getPropertyValue("spouse.spouse.age"); + assertTrue("rod is 31, not" + RA, RA == 31); + ITestBean spousesSpouse = (ITestBean) accessor.getPropertyValue("spouse.spouse"); + assertTrue("spousesSpouse = initial point", target == spousesSpouse); + } + + @Test + public void getPropertyIntermediatePropertyIsNull() { + Person target = createPerson("John", "London", "UK"); + target.address = null; + AbstractPropertyAccessor accessor = createAccessor(target); try { accessor.getPropertyValue("address.country.name"); @@ -128,23 +250,33 @@ public abstract class AbstractConfigurablePropertyAccessorTests { } @Test - public void getPropertyIntermediateFieldIsNullWithAutoGrow() { - Person person = createPerson("John", "London", "UK"); - person.address = null; - ConfigurablePropertyAccessor accessor = createAccessor(person); + public void getPropertyIntermediatePropertyIsNullWithAutoGrow() { + Person target = createPerson("John", "London", "UK"); + target.address = null; + AbstractPropertyAccessor accessor = createAccessor(target); accessor.setAutoGrowNestedPaths(true); assertEquals("DefaultCountry", accessor.getPropertyValue("address.country.name")); } @Test - public void getUnknownField() { - Simple simple = new Simple("John", 2); - ConfigurablePropertyAccessor accessor = createAccessor(simple); + public void getPropertyIntermediateMapEntryIsNullWithAutoGrow() { + Foo target = new Foo(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setConversionService(new DefaultConversionService()); + accessor.setAutoGrowNestedPaths(true); + accessor.setPropertyValue("listOfMaps[0]['luckyNumber']", "9"); + assertEquals("9", target.listOfMaps.get(0).get("luckyNumber")); + } + + @Test + public void getUnknownProperty() { + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); try { accessor.getPropertyValue("foo"); - fail("Should have failed to get an unknown field."); + fail("Should have failed to get an unknown property."); } catch (NotReadablePropertyException e) { assertEquals(Simple.class, e.getBeanClass()); @@ -153,9 +285,9 @@ public abstract class AbstractConfigurablePropertyAccessorTests { } @Test - public void getUnknownNestedField() { - Person person = createPerson("John", "London", "UK"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + public void getUnknownNestedProperty() { + Person target = createPerson("John", "London", "UK"); + AbstractPropertyAccessor accessor = createAccessor(target); thrown.expect(NotReadablePropertyException.class); accessor.getPropertyValue("address.bar"); @@ -163,38 +295,107 @@ public abstract class AbstractConfigurablePropertyAccessorTests { @Test public void setSimpleProperty() { - Simple simple = new Simple("John", 2); - ConfigurablePropertyAccessor accessor = createAccessor(simple); + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("name", "SomeValue"); - assertThat(simple.name, is("SomeValue")); - assertThat(simple.getName(), is("SomeValue")); + assertThat(target.name, is("SomeValue")); + assertThat(target.getName(), is("SomeValue")); } @Test public void setNestedProperty() { - Person person = createPerson("John", "Paris", "FR"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + Person target = createPerson("John", "Paris", "FR"); + AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("address.city", "London"); - assertThat(person.address.city, is("London")); + assertThat(target.address.city, is("London")); + } + + @Test + public void setNestedPropertyPolymorphic() throws Exception { + ITestBean target = new TestBean("rod", 31); + ITestBean kerry = new Employee(); + + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("spouse", kerry); + accessor.setPropertyValue("spouse.age", new Integer(35)); + accessor.setPropertyValue("spouse.name", "Kerry"); + accessor.setPropertyValue("spouse.company", "Lewisham"); + assertTrue("kerry name is Kerry", kerry.getName().equals("Kerry")); + + assertTrue("nested set worked", target.getSpouse() == kerry); + assertTrue("no back relation", kerry.getSpouse() == null); + accessor.setPropertyValue(new PropertyValue("spouse.spouse", target)); + assertTrue("nested set worked", kerry.getSpouse() == target); + + AbstractPropertyAccessor kerryAccessor = createAccessor(kerry); + assertTrue("spouse.spouse.spouse.spouse.company=Lewisham", + "Lewisham".equals(kerryAccessor.getPropertyValue("spouse.spouse.spouse.spouse.company"))); + } + + @Test + public void setAnotherNestedProperty() throws Exception { + ITestBean target = new TestBean("rod", 31); + ITestBean kerry = new TestBean("kerry", 0); + + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("spouse", kerry); + + assertTrue("nested set worked", target.getSpouse() == kerry); + assertTrue("no back relation", kerry.getSpouse() == null); + accessor.setPropertyValue(new PropertyValue("spouse.spouse", target)); + assertTrue("nested set worked", kerry.getSpouse() == target); + assertTrue("kerry age not set", kerry.getAge() == 0); + accessor.setPropertyValue(new PropertyValue("spouse.age", 35)); + assertTrue("Set primitive on spouse", kerry.getAge() == 35); + + assertEquals(kerry, accessor.getPropertyValue("spouse")); + assertEquals(target, accessor.getPropertyValue("spouse.spouse")); + } + + @Test + public void setYetAnotherNestedProperties() { + String doctorCompany = ""; + String lawyerCompany = "Dr. Sueem"; + TestBean target = new TestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("doctor.company", doctorCompany); + accessor.setPropertyValue("lawyer.company", lawyerCompany); + assertEquals(doctorCompany, target.getDoctor().getCompany()); + assertEquals(lawyerCompany, target.getLawyer().getCompany()); } @Test public void setNestedDeepProperty() { - Person person = createPerson("John", "Paris", "FR"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + Person target = createPerson("John", "Paris", "FR"); + AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("address.country.name", "UK"); - assertThat(person.address.country.name, is("UK")); + assertThat(target.address.country.name, is("UK")); } @Test - public void setPropertyIntermediateFieldIsNull() { - Person person = createPerson("John", "Paris", "FR"); - person.address.country = null; - ConfigurablePropertyAccessor accessor = createAccessor(person); + public void testErrorMessageOfNestedProperty() { + ITestBean target = new TestBean(); + ITestBean child = new DifferentTestBean(); + child.setName("test"); + target.setSpouse(child); + AbstractPropertyAccessor accessor = createAccessor(target); + try { + accessor.getPropertyValue("spouse.bla"); + } + catch (NotReadablePropertyException ex) { + assertTrue(ex.getMessage().contains(TestBean.class.getName())); + } + } + + @Test + public void setPropertyIntermediatePropertyIsNull() { + Person target = createPerson("John", "Paris", "FR"); + target.address.country = null; + AbstractPropertyAccessor accessor = createAccessor(target); try { accessor.setPropertyValue("address.country.name", "UK"); @@ -204,28 +405,1082 @@ public abstract class AbstractConfigurablePropertyAccessorTests { assertEquals("address.country", e.getPropertyName()); assertEquals(Person.class, e.getBeanClass()); } - assertThat(person.address.country, is(nullValue())); // Not touched + assertThat(target.address.country, is(nullValue())); // Not touched + } + + @Test + public void setAnotherPropertyIntermediatePropertyIsNull() throws Exception { + ITestBean target = new TestBean("rod", 31); + AbstractPropertyAccessor accessor = createAccessor(target); + try { + accessor.setPropertyValue("spouse.age", new Integer(31)); + fail("Shouldn't have succeeded with null path"); + } + catch (NullValueInNestedPathException ex) { + // expected + assertTrue("it was the spouse property that was null, not " + ex.getPropertyName(), + ex.getPropertyName().equals("spouse")); + } } @Test - public void setPropertyIntermediateFieldIsNullWithAutoGrow() { - Person person = createPerson("John", "Paris", "FR"); - person.address.country = null; - ConfigurablePropertyAccessor accessor = createAccessor(person); + public void setPropertyIntermediatePropertyIsNullWithAutoGrow() { + Person target = createPerson("John", "Paris", "FR"); + target.address.country = null; + AbstractPropertyAccessor accessor = createAccessor(target); accessor.setAutoGrowNestedPaths(true); accessor.setPropertyValue("address.country.name", "UK"); - assertThat(person.address.country.name, is("UK")); + assertThat(target.address.country.name, is("UK")); + } + + @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes") + @Test + public void setPropertyIntermediateListIsNullWithAutoGrow() { + Foo target = new Foo(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setConversionService(new DefaultConversionService()); + accessor.setAutoGrowNestedPaths(true); + Map map = new HashMap(); + map.put("favoriteNumber", "9"); + accessor.setPropertyValue("list[0]", map); + assertEquals(map, target.list.get(0)); + } + + @Test + public void setPropertyIntermediateListIsNullWithNoConversionService() { + Foo target = new Foo(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setAutoGrowNestedPaths(true); + accessor.setPropertyValue("listOfMaps[0]['luckyNumber']", "9"); + assertEquals("9", target.listOfMaps.get(0).get("luckyNumber")); + } + + @Test + public void setPropertyIntermediateListIsNullWithBadConversionService() { + Foo target = new Foo(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setConversionService(new GenericConversionService() { + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + throw new ConversionFailedException(sourceType, targetType, source, null); + } + }); + accessor.setAutoGrowNestedPaths(true); + accessor.setPropertyValue("listOfMaps[0]['luckyNumber']", "9"); + assertEquals("9", target.listOfMaps.get(0).get("luckyNumber")); + } + + + @Test + public void setEmptyPropertyValues() { + TestBean target = new TestBean(); + int age = 50; + String name = "Tony"; + target.setAge(age); + target.setName(name); + try { + AbstractPropertyAccessor accessor = createAccessor(target); + assertTrue("age is OK", target.getAge() == age); + assertTrue("name is OK", name.equals(target.getName())); + accessor.setPropertyValues(new MutablePropertyValues()); + // Check its unchanged + assertTrue("age is OK", target.getAge() == age); + assertTrue("name is OK", name.equals(target.getName())); + } + catch (BeansException ex) { + fail("Shouldn't throw exception when everything is valid"); + } + } + + + @Test + public void setValidPropertyValues() { + TestBean target = new TestBean(); + String newName = "tony"; + int newAge = 65; + String newTouchy = "valid"; + try { + AbstractPropertyAccessor accessor = createAccessor(target); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.addPropertyValue(new PropertyValue("age", newAge)); + pvs.addPropertyValue(new PropertyValue("name", newName)); + pvs.addPropertyValue(new PropertyValue("touchy", newTouchy)); + accessor.setPropertyValues(pvs); + assertTrue("Name property should have changed", target.getName().equals(newName)); + assertTrue("Touchy property should have changed", target.getTouchy().equals(newTouchy)); + assertTrue("Age property should have changed", target.getAge() == newAge); + } + catch (BeansException ex) { + fail("Shouldn't throw exception when everything is valid"); + } + } + + @Test + public void setIndividualValidPropertyValues() { + TestBean target = new TestBean(); + String newName = "tony"; + int newAge = 65; + String newTouchy = "valid"; + try { + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("age", new Integer(newAge)); + accessor.setPropertyValue(new PropertyValue("name", newName)); + accessor.setPropertyValue(new PropertyValue("touchy", newTouchy)); + assertTrue("Name property should have changed", target.getName().equals(newName)); + assertTrue("Touchy property should have changed", target.getTouchy().equals(newTouchy)); + assertTrue("Age property should have changed", target.getAge() == newAge); + } + catch (BeansException ex) { + fail("Shouldn't throw exception when everything is valid"); + } + } + + @Test + public void setPropertyIsReflectedImmediately() { + TestBean target = new TestBean(); + int newAge = 33; + try { + AbstractPropertyAccessor accessor = createAccessor(target); + target.setAge(newAge); + Object bwAge = accessor.getPropertyValue("age"); + assertTrue("Age is an integer", bwAge instanceof Integer); + assertTrue("Bean wrapper must pick up changes", (int) bwAge == newAge); + } + catch (Exception ex) { + fail("Shouldn't throw exception when everything is valid"); + } + } + + @Test + public void setPropertyToNull() { + TestBean target = new TestBean(); + target.setName("Frank"); // we need to change it back + target.setSpouse(target); + AbstractPropertyAccessor accessor = createAccessor(target); + assertTrue("name is not null to start off", target.getName() != null); + accessor.setPropertyValue("name", null); + assertTrue("name is now null", target.getName() == null); + // now test with non-string + assertTrue("spouse is not null to start off", target.getSpouse() != null); + accessor.setPropertyValue("spouse", null); + assertTrue("spouse is now null", target.getSpouse() == null); + } + + + @Test + public void setIndexedPropertyIgnored() { + MutablePropertyValues values = new MutablePropertyValues(); + values.add("toBeIgnored[0]", 42); + AbstractPropertyAccessor accessor = createAccessor(new Object()); + accessor.setPropertyValues(values, true); + } + + @Test + public void setPropertyWithPrimitiveConversion() { + MutablePropertyValues values = new MutablePropertyValues(); + values.add("name", 42); + TestBean target = new TestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValues(values); + assertEquals("42", target.getName()); + } + + @Test + public void setPropertyWithCustomEditor() { + MutablePropertyValues values = new MutablePropertyValues(); + values.add("name", Integer.class); + TestBean target = new TestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(String.class, new PropertyEditorSupport() { + @Override + public void setValue(Object value) { + super.setValue(value.toString()); + } + }); + accessor.setPropertyValues(values); + assertEquals(Integer.class.toString(), target.getName()); + } + + @Test + public void setStringPropertyWithCustomEditor() throws Exception { + TestBean target = new TestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(String.class, "name", new PropertyEditorSupport() { + @Override + public void setValue(Object value) { + if (value instanceof String[]) { + setValue(StringUtils.arrayToDelimitedString(((String[]) value), "-")); + } + else { + super.setValue(value != null ? value : ""); + } + } + }); + accessor.setPropertyValue("name", new String[] {}); + assertEquals("", target.getName()); + accessor.setPropertyValue("name", new String[] {"a1", "b2"}); + assertEquals("a1-b2", target.getName()); + accessor.setPropertyValue("name", null); + assertEquals("", target.getName()); + } + + @Test + public void setBooleanProperty() { + BooleanTestBean target = new BooleanTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + + accessor.setPropertyValue("bool2", "true"); + assertTrue("Correct bool2 value", Boolean.TRUE.equals(accessor.getPropertyValue("bool2"))); + assertTrue("Correct bool2 value", target.getBool2()); + + accessor.setPropertyValue("bool2", "false"); + assertTrue("Correct bool2 value", Boolean.FALSE.equals(accessor.getPropertyValue("bool2"))); + assertTrue("Correct bool2 value", !target.getBool2()); + } + + @Test + public void setNumberProperties() { + NumberTestBean target = new NumberTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + + try { + accessor.setPropertyValue("short2", "2"); + accessor.setPropertyValue("int2", "8"); + accessor.setPropertyValue("long2", "6"); + accessor.setPropertyValue("bigInteger", "3"); + accessor.setPropertyValue("float2", "8.1"); + accessor.setPropertyValue("double2", "6.1"); + accessor.setPropertyValue("bigDecimal", "4.0"); + } + catch (BeansException ex) { + fail("Should not throw BeansException: " + ex.getMessage()); + } + + assertTrue("Correct short2 value", new Short("2").equals(accessor.getPropertyValue("short2"))); + assertTrue("Correct short2 value", new Short("2").equals(target.getShort2())); + assertTrue("Correct int2 value", new Integer("8").equals(accessor.getPropertyValue("int2"))); + assertTrue("Correct int2 value", new Integer("8").equals(target.getInt2())); + assertTrue("Correct long2 value", new Long("6").equals(accessor.getPropertyValue("long2"))); + assertTrue("Correct long2 value", new Long("6").equals(target.getLong2())); + assertTrue("Correct bigInteger value", new BigInteger("3").equals(accessor.getPropertyValue("bigInteger"))); + assertTrue("Correct bigInteger value", new BigInteger("3").equals(target.getBigInteger())); + assertTrue("Correct float2 value", new Float("8.1").equals(accessor.getPropertyValue("float2"))); + assertTrue("Correct float2 value", new Float("8.1").equals(target.getFloat2())); + assertTrue("Correct double2 value", new Double("6.1").equals(accessor.getPropertyValue("double2"))); + assertTrue("Correct double2 value", new Double("6.1").equals(target.getDouble2())); + assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(accessor.getPropertyValue("bigDecimal"))); + assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(target.getBigDecimal())); + } + + @Test + public void setNumberPropertiesWithCoercion() { + NumberTestBean target = new NumberTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + + try { + accessor.setPropertyValue("short2", new Integer(2)); + accessor.setPropertyValue("int2", new Long(8)); + accessor.setPropertyValue("long2", new BigInteger("6")); + accessor.setPropertyValue("bigInteger", new Integer(3)); + accessor.setPropertyValue("float2", new Double(8.1)); + accessor.setPropertyValue("double2", new BigDecimal(6.1)); + accessor.setPropertyValue("bigDecimal", new Float(4.0)); + } + catch (BeansException ex) { + fail("Should not throw BeansException: " + ex.getMessage()); + } + + assertTrue("Correct short2 value", new Short("2").equals(accessor.getPropertyValue("short2"))); + assertTrue("Correct short2 value", new Short("2").equals(target.getShort2())); + assertTrue("Correct int2 value", new Integer("8").equals(accessor.getPropertyValue("int2"))); + assertTrue("Correct int2 value", new Integer("8").equals(target.getInt2())); + assertTrue("Correct long2 value", new Long("6").equals(accessor.getPropertyValue("long2"))); + assertTrue("Correct long2 value", new Long("6").equals(target.getLong2())); + assertTrue("Correct bigInteger value", new BigInteger("3").equals(accessor.getPropertyValue("bigInteger"))); + assertTrue("Correct bigInteger value", new BigInteger("3").equals(target.getBigInteger())); + assertTrue("Correct float2 value", new Float("8.1").equals(accessor.getPropertyValue("float2"))); + assertTrue("Correct float2 value", new Float("8.1").equals(target.getFloat2())); + assertTrue("Correct double2 value", new Double("6.1").equals(accessor.getPropertyValue("double2"))); + assertTrue("Correct double2 value", new Double("6.1").equals(target.getDouble2())); + assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(accessor.getPropertyValue("bigDecimal"))); + assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(target.getBigDecimal())); + } + + @Test + public void setPrimitiveProperties() { + NumberPropertyBean target = new NumberPropertyBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + + String byteValue = " " + Byte.MAX_VALUE + " "; + String shortValue = " " + Short.MAX_VALUE + " "; + String intValue = " " + Integer.MAX_VALUE + " "; + String longValue = " " + Long.MAX_VALUE + " "; + String floatValue = " " + Float.MAX_VALUE + " "; + String doubleValue = " " + Double.MAX_VALUE + " "; + + accessor.setPropertyValue("myPrimitiveByte", byteValue); + accessor.setPropertyValue("myByte", byteValue); + + accessor.setPropertyValue("myPrimitiveShort", shortValue); + accessor.setPropertyValue("myShort", shortValue); + + accessor.setPropertyValue("myPrimitiveInt", intValue); + accessor.setPropertyValue("myInteger", intValue); + + accessor.setPropertyValue("myPrimitiveLong", longValue); + accessor.setPropertyValue("myLong", longValue); + + accessor.setPropertyValue("myPrimitiveFloat", floatValue); + accessor.setPropertyValue("myFloat", floatValue); + + accessor.setPropertyValue("myPrimitiveDouble", doubleValue); + accessor.setPropertyValue("myDouble", doubleValue); + + assertEquals(Byte.MAX_VALUE, target.getMyPrimitiveByte()); + assertEquals(Byte.MAX_VALUE, target.getMyByte().byteValue()); + + assertEquals(Short.MAX_VALUE, target.getMyPrimitiveShort()); + assertEquals(Short.MAX_VALUE, target.getMyShort().shortValue()); + + assertEquals(Integer.MAX_VALUE, target.getMyPrimitiveInt()); + assertEquals(Integer.MAX_VALUE, target.getMyInteger().intValue()); + + assertEquals(Long.MAX_VALUE, target.getMyPrimitiveLong()); + assertEquals(Long.MAX_VALUE, target.getMyLong().longValue()); + + assertEquals(Float.MAX_VALUE, target.getMyPrimitiveFloat(), 0.001); + assertEquals(Float.MAX_VALUE, target.getMyFloat().floatValue(), 0.001); + + assertEquals(Double.MAX_VALUE, target.getMyPrimitiveDouble(), 0.001); + assertEquals(Double.MAX_VALUE, target.getMyDouble().doubleValue(), 0.001); + + } + + @Test + public void setEnumProperty() { + EnumTester target = new EnumTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + + accessor.setPropertyValue("autowire", "BY_NAME"); + assertEquals(Autowire.BY_NAME, target.getAutowire()); + + accessor.setPropertyValue("autowire", " BY_TYPE "); + assertEquals(Autowire.BY_TYPE, target.getAutowire()); + + try { + accessor.setPropertyValue("autowire", "NHERITED"); + fail("Should have thrown TypeMismatchException"); + } + catch (TypeMismatchException ex) { + // expected + } + } + + @Test + public void setGenericEnumProperty() { + EnumConsumer target = new EnumConsumer(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE"); + assertEquals(TestEnum.TEST_VALUE, target.getEnumValue()); + } + + @Test + public void setWildcardEnumProperty() { + WildcardEnumConsumer target = new WildcardEnumConsumer(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE"); + assertEquals(TestEnum.TEST_VALUE, target.getEnumValue()); + } + + @Test + public void setPropertiesProperty() throws Exception { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("name", "ptest"); + + // Note format... + String ps = "peace=war\nfreedom=slavery"; + accessor.setPropertyValue("properties", ps); + + assertTrue("name was set", target.name.equals("ptest")); + assertTrue("properties non null", target.properties != null); + String freedomVal = target.properties.getProperty("freedom"); + String peaceVal = target.properties.getProperty("peace"); + assertTrue("peace==war", peaceVal.equals("war")); + assertTrue("Freedom==slavery", freedomVal.equals("slavery")); + } + + @Test + public void setStringArrayProperty() throws Exception { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + + accessor.setPropertyValue("stringArray", new String[] {"foo", "fi", "fi", "fum"}); + assertTrue("stringArray length = 4", target.stringArray.length == 4); + assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && + target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")); + + List list = new ArrayList(); + list.add("foo"); + list.add("fi"); + list.add("fi"); + list.add("fum"); + accessor.setPropertyValue("stringArray", list); + assertTrue("stringArray length = 4", target.stringArray.length == 4); + assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && + target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")); + + Set set = new HashSet(); + set.add("foo"); + set.add("fi"); + set.add("fum"); + accessor.setPropertyValue("stringArray", set); + assertTrue("stringArray length = 3", target.stringArray.length == 3); + List result = Arrays.asList(target.stringArray); + assertTrue("correct values", result.contains("foo") && result.contains("fi") && result.contains("fum")); + + accessor.setPropertyValue("stringArray", "one"); + assertTrue("stringArray length = 1", target.stringArray.length == 1); + assertTrue("stringArray elt is ok", target.stringArray[0].equals("one")); + + accessor.setPropertyValue("stringArray", null); + assertTrue("stringArray is null", target.stringArray == null); + } + + @Test + public void setStringArrayPropertyWithCustomStringEditor() throws Exception { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() { + @Override + public void setAsText(String text) { + setValue(text.substring(1)); + } + }); + + accessor.setPropertyValue("stringArray", new String[] {"4foo", "7fi", "6fi", "5fum"}); + assertTrue("stringArray length = 4", target.stringArray.length == 4); + assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && + target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")); + + List list = new ArrayList(); + list.add("4foo"); + list.add("7fi"); + list.add("6fi"); + list.add("5fum"); + accessor.setPropertyValue("stringArray", list); + assertTrue("stringArray length = 4", target.stringArray.length == 4); + assertTrue("correct values", target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && + target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")); + + Set set = new HashSet(); + set.add("4foo"); + set.add("7fi"); + set.add("6fum"); + accessor.setPropertyValue("stringArray", set); + assertTrue("stringArray length = 3", target.stringArray.length == 3); + List result = Arrays.asList(target.stringArray); + assertTrue("correct values", result.contains("foo") && result.contains("fi") && result.contains("fum")); + + accessor.setPropertyValue("stringArray", "8one"); + assertTrue("stringArray length = 1", target.stringArray.length == 1); + assertTrue("correct values", target.stringArray[0].equals("one")); + } + + @Test + public void setStringArrayPropertyWithStringSplitting() throws Exception { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.useConfigValueEditors(); + accessor.setPropertyValue("stringArray", "a1,b2"); + assertTrue("stringArray length = 2", target.stringArray.length == 2); + assertTrue("correct values", target.stringArray[0].equals("a1") && target.stringArray[1].equals("b2")); + } + + @Test + public void setStringArrayPropertyWithCustomStringDelimiter() throws Exception { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(String[].class, "stringArray", new StringArrayPropertyEditor("-")); + accessor.setPropertyValue("stringArray", "a1-b2"); + assertTrue("stringArray length = 2", target.stringArray.length == 2); + assertTrue("correct values", target.stringArray[0].equals("a1") && target.stringArray[1].equals("b2")); + } + + @Test + public void setStringArrayWithAutoGrow() throws Exception { + StringArrayBean target = new StringArrayBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setAutoGrowNestedPaths(true); + + accessor.setPropertyValue("array[0]", "Test0"); + assertEquals(1, target.getArray().length); + + accessor.setPropertyValue("array[2]", "Test2"); + assertEquals(3, target.getArray().length); + assertTrue("correct values", target.getArray()[0].equals("Test0") && target.getArray()[1] == null && + target.getArray()[2].equals("Test2")); + } + + @Test + public void setIntArrayProperty() { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + + accessor.setPropertyValue("intArray", new int[] {4, 5, 2, 3}); + assertTrue("intArray length = 4", target.intArray.length == 4); + assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 && + target.intArray[2] == 2 && target.intArray[3] == 3); + + accessor.setPropertyValue("intArray", new String[] {"4", "5", "2", "3"}); + assertTrue("intArray length = 4", target.intArray.length == 4); + assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 && + target.intArray[2] == 2 && target.intArray[3] == 3); + + List list = new ArrayList<>(); + list.add(4); + list.add("5"); + list.add(2); + list.add("3"); + accessor.setPropertyValue("intArray", list); + assertTrue("intArray length = 4", target.intArray.length == 4); + assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 && + target.intArray[2] == 2 && target.intArray[3] == 3); + + Set set = new HashSet<>(); + set.add("4"); + set.add(5); + set.add("3"); + accessor.setPropertyValue("intArray", set); + assertTrue("intArray length = 3", target.intArray.length == 3); + List result = new ArrayList<>(); + result.add(target.intArray[0]); + result.add(target.intArray[1]); + result.add(target.intArray[2]); + assertTrue("correct values", result.contains(new Integer(4)) && result.contains(new Integer(5)) && + result.contains(new Integer(3))); + + accessor.setPropertyValue("intArray", new Integer[] {1}); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + + accessor.setPropertyValue("intArray", new Integer(1)); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + + accessor.setPropertyValue("intArray", new String[] {"1"}); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + + accessor.setPropertyValue("intArray", "1"); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + } + + @Test + public void setIntArrayPropertyWithCustomEditor() { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(int.class, new PropertyEditorSupport() { + @Override + public void setAsText(String text) { + setValue(new Integer(Integer.parseInt(text) + 1)); + } + }); + + accessor.setPropertyValue("intArray", new int[] {4, 5, 2, 3}); + assertTrue("intArray length = 4", target.intArray.length == 4); + assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 && + target.intArray[2] == 2 && target.intArray[3] == 3); + + accessor.setPropertyValue("intArray", new String[] {"3", "4", "1", "2"}); + assertTrue("intArray length = 4", target.intArray.length == 4); + assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5 && + target.intArray[2] == 2 && target.intArray[3] == 3); + + accessor.setPropertyValue("intArray", new Integer(1)); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + + accessor.setPropertyValue("intArray", new String[] {"0"}); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + + accessor.setPropertyValue("intArray", "0"); + assertTrue("intArray length = 4", target.intArray.length == 1); + assertTrue("correct values", target.intArray[0] == 1); + } + + @Test + public void setIntArrayPropertyWithStringSplitting() throws Exception { + PropsTester target = new PropsTester(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.useConfigValueEditors(); + accessor.setPropertyValue("intArray", "4,5"); + assertTrue("intArray length = 2", target.intArray.length == 2); + assertTrue("correct values", target.intArray[0] == 4 && target.intArray[1] == 5); + } + + @Test + public void setPrimitiveArrayProperty() { + PrimitiveArrayBean target = new PrimitiveArrayBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("array", new String[] {"1", "2"}); + assertEquals(2, target.getArray().length); + assertEquals(1, target.getArray()[0]); + assertEquals(2, target.getArray()[1]); + } + + @Test + public void setPrimitiveArrayPropertyLargeMatching() { + Assume.group(TestGroup.PERFORMANCE); + Assume.notLogging(LogFactory.getLog(AbstractConfigurablePropertyAccessorTests.class)); + + PrimitiveArrayBean target = new PrimitiveArrayBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + int[] input = new int[1024]; + StopWatch sw = new StopWatch(); + sw.start("array1"); + for (int i = 0; i < 1000; i++) { + accessor.setPropertyValue("array", input); + } + sw.stop(); + assertEquals(1024, target.getArray().length); + assertEquals(0, target.getArray()[0]); + long time1 = sw.getLastTaskTimeMillis(); + assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100); + + accessor.registerCustomEditor(String.class, new StringTrimmerEditor(false)); + sw.start("array2"); + for (int i = 0; i < 1000; i++) { + accessor.setPropertyValue("array", input); + } + sw.stop(); + assertTrue("Took too long", sw.getLastTaskTimeMillis() < 125); + + accessor.registerCustomEditor(int.class, "array.somePath", new CustomNumberEditor(Integer.class, false)); + sw.start("array3"); + for (int i = 0; i < 1000; i++) { + accessor.setPropertyValue("array", input); + } + sw.stop(); + assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100); + + accessor.registerCustomEditor(int.class, "array[0].somePath", new CustomNumberEditor(Integer.class, false)); + sw.start("array3"); + for (int i = 0; i < 1000; i++) { + accessor.setPropertyValue("array", input); + } + sw.stop(); + assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100); + + accessor.registerCustomEditor(int.class, new CustomNumberEditor(Integer.class, false)); + sw.start("array4"); + for (int i = 0; i < 100; i++) { + accessor.setPropertyValue("array", input); + } + sw.stop(); + assertEquals(1024, target.getArray().length); + assertEquals(0, target.getArray()[0]); + assertTrue("Took too long", sw.getLastTaskTimeMillis() > time1); + } + + @Test + public void setPrimitiveArrayPropertyLargeMatchingWithSpecificEditor() { + PrimitiveArrayBean target = new PrimitiveArrayBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(int.class, "array", new PropertyEditorSupport() { + @Override + public void setValue(Object value) { + if (value instanceof Integer) { + super.setValue(new Integer((Integer) value + 1)); + } + } + }); + int[] input = new int[1024]; + accessor.setPropertyValue("array", input); + assertEquals(1024, target.getArray().length); + assertEquals(1, target.getArray()[0]); + assertEquals(1, target.getArray()[1]); + } + + @Test + public void setPrimitiveArrayPropertyLargeMatchingWithIndexSpecificEditor() { + PrimitiveArrayBean target = new PrimitiveArrayBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(int.class, "array[1]", new PropertyEditorSupport() { + @Override + public void setValue(Object value) { + if (value instanceof Integer) { + super.setValue(new Integer((Integer) value + 1)); + } + } + }); + int[] input = new int[1024]; + accessor.setPropertyValue("array", input); + assertEquals(1024, target.getArray().length); + assertEquals(0, target.getArray()[0]); + assertEquals(1, target.getArray()[1]); + } + + @Test + public void setPrimitiveArrayPropertyWithAutoGrow() throws Exception { + PrimitiveArrayBean target = new PrimitiveArrayBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setAutoGrowNestedPaths(true); + + accessor.setPropertyValue("array[0]", 1); + assertEquals(1, target.getArray().length); + + accessor.setPropertyValue("array[2]", 3); + assertEquals(3, target.getArray().length); + assertTrue("correct values", target.getArray()[0] == 1 && target.getArray()[1] == 0 && + target.getArray()[2] == 3); + } + + @Test + public void setGenericArrayProperty() { + SkipReaderStub target = new SkipReaderStub(); + AbstractPropertyAccessor accessor = createAccessor(target); + List values = new LinkedList(); + values.add("1"); + values.add("2"); + values.add("3"); + values.add("4"); + accessor.setPropertyValue("items", values); + Object[] result = target.items; + assertEquals(4, result.length); + assertEquals("1", result[0]); + assertEquals("2", result[1]); + assertEquals("3", result[2]); + assertEquals("4", result[3]); + } + + @Test + public void setArrayPropertyToObject() { + ArrayToObject target = new ArrayToObject(); + AbstractPropertyAccessor accessor = createAccessor(target); + + Object[] array = new Object[] {"1", "2"}; + accessor.setPropertyValue("object", array); + assertThat(target.getObject(), equalTo((Object) array)); + + array = new Object[] {"1"}; + accessor.setPropertyValue("object", array); + assertThat(target.getObject(), equalTo((Object) array)); + } + + + @Test + public void setCollectionProperty() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Collection coll = new HashSet(); + coll.add("coll1"); + accessor.setPropertyValue("collection", coll); + Set set = new HashSet(); + set.add("set1"); + accessor.setPropertyValue("set", set); + SortedSet sortedSet = new TreeSet(); + sortedSet.add("sortedSet1"); + accessor.setPropertyValue("sortedSet", sortedSet); + List list = new LinkedList(); + list.add("list1"); + accessor.setPropertyValue("list", list); + assertSame(coll, target.getCollection()); + assertSame(set, target.getSet()); + assertSame(sortedSet, target.getSortedSet()); + assertSame(list, target.getList()); + } + + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + @Test + public void setCollectionPropertyNonMatchingType() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Collection coll = new ArrayList(); + coll.add("coll1"); + accessor.setPropertyValue("collection", coll); + List set = new LinkedList(); + set.add("set1"); + accessor.setPropertyValue("set", set); + List sortedSet = new ArrayList(); + sortedSet.add("sortedSet1"); + accessor.setPropertyValue("sortedSet", sortedSet); + Set list = new HashSet(); + list.add("list1"); + accessor.setPropertyValue("list", list); + assertEquals(1, target.getCollection().size()); + assertTrue(target.getCollection().containsAll(coll)); + assertEquals(1, target.getSet().size()); + assertTrue(target.getSet().containsAll(set)); + assertEquals(1, target.getSortedSet().size()); + assertTrue(target.getSortedSet().containsAll(sortedSet)); + assertEquals(1, target.getList().size()); + assertTrue(target.getList().containsAll(list)); + } + + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + @Test + public void setCollectionPropertyWithArrayValue() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Collection coll = new HashSet(); + coll.add("coll1"); + accessor.setPropertyValue("collection", coll.toArray()); + List set = new LinkedList(); + set.add("set1"); + accessor.setPropertyValue("set", set.toArray()); + List sortedSet = new ArrayList(); + sortedSet.add("sortedSet1"); + accessor.setPropertyValue("sortedSet", sortedSet.toArray()); + Set list = new HashSet(); + list.add("list1"); + accessor.setPropertyValue("list", list.toArray()); + assertEquals(1, target.getCollection().size()); + assertTrue(target.getCollection().containsAll(coll)); + assertEquals(1, target.getSet().size()); + assertTrue(target.getSet().containsAll(set)); + assertEquals(1, target.getSortedSet().size()); + assertTrue(target.getSortedSet().containsAll(sortedSet)); + assertEquals(1, target.getList().size()); + assertTrue(target.getList().containsAll(list)); + } + + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + @Test + public void setCollectionPropertyWithIntArrayValue() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Collection coll = new HashSet(); + coll.add(0); + accessor.setPropertyValue("collection", new int[] {0}); + List set = new LinkedList(); + set.add(1); + accessor.setPropertyValue("set", new int[] {1}); + List sortedSet = new ArrayList(); + sortedSet.add(2); + accessor.setPropertyValue("sortedSet", new int[] {2}); + Set list = new HashSet(); + list.add(3); + accessor.setPropertyValue("list", new int[] {3}); + assertEquals(1, target.getCollection().size()); + assertTrue(target.getCollection().containsAll(coll)); + assertEquals(1, target.getSet().size()); + assertTrue(target.getSet().containsAll(set)); + assertEquals(1, target.getSortedSet().size()); + assertTrue(target.getSortedSet().containsAll(sortedSet)); + assertEquals(1, target.getList().size()); + assertTrue(target.getList().containsAll(list)); + } + + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + @Test + public void setCollectionPropertyWithIntegerValue() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Collection coll = new HashSet(); + coll.add(0); + accessor.setPropertyValue("collection", new Integer(0)); + List set = new LinkedList(); + set.add(1); + accessor.setPropertyValue("set", new Integer(1)); + List sortedSet = new ArrayList(); + sortedSet.add(2); + accessor.setPropertyValue("sortedSet", new Integer(2)); + Set list = new HashSet(); + list.add(3); + accessor.setPropertyValue("list", new Integer(3)); + assertEquals(1, target.getCollection().size()); + assertTrue(target.getCollection().containsAll(coll)); + assertEquals(1, target.getSet().size()); + assertTrue(target.getSet().containsAll(set)); + assertEquals(1, target.getSortedSet().size()); + assertTrue(target.getSortedSet().containsAll(sortedSet)); + assertEquals(1, target.getList().size()); + assertTrue(target.getList().containsAll(list)); + } + + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + @Test + public void setCollectionPropertyWithStringValue() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + List set = new LinkedList(); + set.add("set1"); + accessor.setPropertyValue("set", "set1"); + List sortedSet = new ArrayList(); + sortedSet.add("sortedSet1"); + accessor.setPropertyValue("sortedSet", "sortedSet1"); + Set list = new HashSet(); + list.add("list1"); + accessor.setPropertyValue("list", "list1"); + assertEquals(1, target.getSet().size()); + assertTrue(target.getSet().containsAll(set)); + assertEquals(1, target.getSortedSet().size()); + assertTrue(target.getSortedSet().containsAll(sortedSet)); + assertEquals(1, target.getList().size()); + assertTrue(target.getList().containsAll(list)); + } + + @Test + public void setCollectionPropertyWithStringValueAndCustomEditor() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(String.class, "set", new StringTrimmerEditor(false)); + accessor.registerCustomEditor(String.class, "list", new StringTrimmerEditor(false)); + + accessor.setPropertyValue("set", "set1 "); + accessor.setPropertyValue("sortedSet", "sortedSet1"); + accessor.setPropertyValue("list", "list1 "); + assertEquals(1, target.getSet().size()); + assertTrue(target.getSet().contains("set1")); + assertEquals(1, target.getSortedSet().size()); + assertTrue(target.getSortedSet().contains("sortedSet1")); + assertEquals(1, target.getList().size()); + assertTrue(target.getList().contains("list1")); + + accessor.setPropertyValue("list", Collections.singletonList("list1 ")); + assertTrue(target.getList().contains("list1")); + } + + @Test + public void setMapProperty() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Map map = new HashMap(); + map.put("key", "value"); + accessor.setPropertyValue("map", map); + SortedMap sortedMap = new TreeMap<>(); + map.put("sortedKey", "sortedValue"); + accessor.setPropertyValue("sortedMap", sortedMap); + assertSame(map, target.getMap()); + assertSame(sortedMap, target.getSortedMap()); + } + + @Test + public void setMapPropertyNonMatchingType() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Map map = new TreeMap(); + map.put("key", "value"); + accessor.setPropertyValue("map", map); + Map sortedMap = new TreeMap(); + sortedMap.put("sortedKey", "sortedValue"); + accessor.setPropertyValue("sortedMap", sortedMap); + assertEquals(1, target.getMap().size()); + assertEquals("value", target.getMap().get("key")); + assertEquals(1, target.getSortedMap().size()); + assertEquals("sortedValue", target.getSortedMap().get("sortedKey")); + } + + @Test + public void setMapPropertyWithTypeConversion() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(TestBean.class, new PropertyEditorSupport() { + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (!StringUtils.hasLength(text)) { + throw new IllegalArgumentException(); + } + setValue(new TestBean(text)); + } + }); + + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("map[key1]", "rod"); + pvs.add("map[key2]", "rob"); + accessor.setPropertyValues(pvs); + assertEquals("rod", ((TestBean) target.getMap().get("key1")).getName()); + assertEquals("rob", ((TestBean) target.getMap().get("key2")).getName()); + + pvs = new MutablePropertyValues(); + pvs.add("map[key1]", "rod"); + pvs.add("map[key2]", ""); + try { + accessor.setPropertyValues(pvs); + fail("Should have thrown TypeMismatchException"); + } + catch (PropertyBatchUpdateException ex) { + PropertyAccessException pae = ex.getPropertyAccessException("map[key2]"); + assertTrue(pae instanceof TypeMismatchException); + } + } + + @Test + public void setMapPropertyWithUnmodifiableMap() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() { + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (!StringUtils.hasLength(text)) { + throw new IllegalArgumentException(); + } + setValue(new TestBean(text)); + } + }); + + Map inputMap = new HashMap(); + inputMap.put(1, "rod"); + inputMap.put(2, "rob"); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("map", Collections.unmodifiableMap(inputMap)); + accessor.setPropertyValues(pvs); + assertEquals("rod", ((TestBean) target.getMap().get(1)).getName()); + assertEquals("rob", ((TestBean) target.getMap().get(2)).getName()); + } + + @Test + public void setMapPropertyWithCustomUnmodifiableMap() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() { + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (!StringUtils.hasLength(text)) { + throw new IllegalArgumentException(); + } + setValue(new TestBean(text)); + } + }); + + Map inputMap = new HashMap(); + inputMap.put(1, "rod"); + inputMap.put(2, "rob"); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("map", new ReadOnlyMap<>(inputMap)); + accessor.setPropertyValues(pvs); + assertEquals("rod", ((TestBean) target.getMap().get(1)).getName()); + assertEquals("rob", ((TestBean) target.getMap().get(2)).getName()); + } + + @SuppressWarnings("unchecked") // must work with raw map in this test + @Test + public void setRawMapPropertyWithNoEditorRegistered() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + Map inputMap = new HashMap(); + inputMap.put(1, "rod"); + inputMap.put(2, "rob"); + ReadOnlyMap readOnlyMap = new ReadOnlyMap(inputMap); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("map", readOnlyMap); + accessor.setPropertyValues(pvs); + assertSame(readOnlyMap, target.getMap()); + assertFalse(readOnlyMap.isAccessed()); } @Test - public void setUnknownField() { - Simple simple = new Simple("John", 2); - ConfigurablePropertyAccessor accessor = createAccessor(simple); + public void setUnknownProperty() { + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); try { accessor.setPropertyValue("foo", "value"); - fail("Should have failed to set an unknown field."); + fail("Should have failed to set an unknown property."); } catch (NotWritablePropertyException e) { assertEquals(Simple.class, e.getBeanClass()); @@ -234,47 +1489,264 @@ public abstract class AbstractConfigurablePropertyAccessorTests { } @Test - public void setUnknownNestedField() { - Person person = createPerson("John", "Paris", "FR"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + public void setUnknownOptionalProperty() { + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); + + try { + PropertyValue value = new PropertyValue("foo", "value"); + value.setOptional(true); + accessor.setPropertyValue(value); + } + catch (NotWritablePropertyException e) { + fail("Should not have failed to set an unknown optional property."); + } + } + + @Test + public void setPropertyInProtectedBaseBean() { + DerivedFromProtectedBaseBean target = new DerivedFromProtectedBaseBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("someProperty", "someValue"); + assertEquals("someValue", accessor.getPropertyValue("someProperty")); + assertEquals("someValue", target.getSomeProperty()); + } + + @Test + public void setPropertyTypeMismatch() { + TestBean target = new TestBean(); + try { + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("age", "foobar"); + fail("Should throw exception on type mismatch"); + } + catch (TypeMismatchException ex) { + // expected + } + } + + @Test + public void setEmptyValueForPrimitiveProperty() { + TestBean target = new TestBean(); + try { + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("age", ""); + fail("Should throw exception on type mismatch"); + } + catch (TypeMismatchException ex) { + // expected + } + catch (Exception ex) { + fail("Shouldn't throw exception other than Type mismatch"); + } + } + + @Test + public void setUnknownNestedProperty() { + Person target = createPerson("John", "Paris", "FR"); + AbstractPropertyAccessor accessor = createAccessor(target); thrown.expect(NotWritablePropertyException.class); accessor.setPropertyValue("address.bar", "value"); } + @Test + public void setPropertyValuesIgnoresInvalidNestedOnRequest() { + ITestBean target = new TestBean(); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.addPropertyValue(new PropertyValue("name", "rod")); + pvs.addPropertyValue(new PropertyValue("graceful.rubbish", "tony")); + pvs.addPropertyValue(new PropertyValue("more.garbage", new Object())); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValues(pvs, true); + assertTrue("Set valid and ignored invalid", target.getName().equals("rod")); + try { + // Don't ignore: should fail + accessor.setPropertyValues(pvs, false); + fail("Shouldn't have ignored invalid updates"); + } + catch (NotWritablePropertyException ex) { + // OK: but which exception?? + } + } + + @Test + public void getAndSetIndexedProperties() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + TestBean tb0 = target.getArray()[0]; + TestBean tb1 = target.getArray()[1]; + TestBean tb2 = ((TestBean) target.getList().get(0)); + TestBean tb3 = ((TestBean) target.getList().get(1)); + TestBean tb6 = ((TestBean) target.getSet().toArray()[0]); + TestBean tb7 = ((TestBean) target.getSet().toArray()[1]); + TestBean tb4 = ((TestBean) target.getMap().get("key1")); + TestBean tb5 = ((TestBean) target.getMap().get("key.3")); + assertEquals("name0", tb0.getName()); + assertEquals("name1", tb1.getName()); + assertEquals("name2", tb2.getName()); + assertEquals("name3", tb3.getName()); + assertEquals("name6", tb6.getName()); + assertEquals("name7", tb7.getName()); + assertEquals("name4", tb4.getName()); + assertEquals("name5", tb5.getName()); + assertEquals("name0", accessor.getPropertyValue("array[0].name")); + assertEquals("name1", accessor.getPropertyValue("array[1].name")); + assertEquals("name2", accessor.getPropertyValue("list[0].name")); + assertEquals("name3", accessor.getPropertyValue("list[1].name")); + assertEquals("name6", accessor.getPropertyValue("set[0].name")); + assertEquals("name7", accessor.getPropertyValue("set[1].name")); + assertEquals("name4", accessor.getPropertyValue("map[key1].name")); + assertEquals("name5", accessor.getPropertyValue("map[key.3].name")); + assertEquals("name4", accessor.getPropertyValue("map['key1'].name")); + assertEquals("name5", accessor.getPropertyValue("map[\"key.3\"].name")); + assertEquals("nameX", accessor.getPropertyValue("map[key4][0].name")); + assertEquals("nameY", accessor.getPropertyValue("map[key4][1].name")); + + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("array[0].name", "name5"); + pvs.add("array[1].name", "name4"); + pvs.add("list[0].name", "name3"); + pvs.add("list[1].name", "name2"); + pvs.add("set[0].name", "name8"); + pvs.add("set[1].name", "name9"); + pvs.add("map[key1].name", "name1"); + pvs.add("map['key.3'].name", "name0"); + pvs.add("map[key4][0].name", "nameA"); + pvs.add("map[key4][1].name", "nameB"); + accessor.setPropertyValues(pvs); + assertEquals("name5", tb0.getName()); + assertEquals("name4", tb1.getName()); + assertEquals("name3", tb2.getName()); + assertEquals("name2", tb3.getName()); + assertEquals("name1", tb4.getName()); + assertEquals("name0", tb5.getName()); + assertEquals("name5", accessor.getPropertyValue("array[0].name")); + assertEquals("name4", accessor.getPropertyValue("array[1].name")); + assertEquals("name3", accessor.getPropertyValue("list[0].name")); + assertEquals("name2", accessor.getPropertyValue("list[1].name")); + assertEquals("name8", accessor.getPropertyValue("set[0].name")); + assertEquals("name9", accessor.getPropertyValue("set[1].name")); + assertEquals("name1", accessor.getPropertyValue("map[\"key1\"].name")); + assertEquals("name0", accessor.getPropertyValue("map['key.3'].name")); + assertEquals("nameA", accessor.getPropertyValue("map[key4][0].name")); + assertEquals("nameB", accessor.getPropertyValue("map[key4][1].name")); + } + + @Test + public void getAndSetIndexedPropertiesWithDirectAccess() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + TestBean tb0 = target.getArray()[0]; + TestBean tb1 = target.getArray()[1]; + TestBean tb2 = ((TestBean) target.getList().get(0)); + TestBean tb3 = ((TestBean) target.getList().get(1)); + TestBean tb6 = ((TestBean) target.getSet().toArray()[0]); + TestBean tb7 = ((TestBean) target.getSet().toArray()[1]); + TestBean tb4 = ((TestBean) target.getMap().get("key1")); + TestBean tb5 = ((TestBean) target.getMap().get("key2")); + assertEquals(tb0, accessor.getPropertyValue("array[0]")); + assertEquals(tb1, accessor.getPropertyValue("array[1]")); + assertEquals(tb2, accessor.getPropertyValue("list[0]")); + assertEquals(tb3, accessor.getPropertyValue("list[1]")); + assertEquals(tb6, accessor.getPropertyValue("set[0]")); + assertEquals(tb7, accessor.getPropertyValue("set[1]")); + assertEquals(tb4, accessor.getPropertyValue("map[key1]")); + assertEquals(tb5, accessor.getPropertyValue("map[key2]")); + assertEquals(tb4, accessor.getPropertyValue("map['key1']")); + assertEquals(tb5, accessor.getPropertyValue("map[\"key2\"]")); + + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("array[0]", tb5); + pvs.add("array[1]", tb4); + pvs.add("list[0]", tb3); + pvs.add("list[1]", tb2); + pvs.add("list[2]", tb0); + pvs.add("list[4]", tb1); + pvs.add("map[key1]", tb1); + pvs.add("map['key2']", tb0); + pvs.add("map[key5]", tb4); + pvs.add("map['key9']", tb5); + accessor.setPropertyValues(pvs); + assertEquals(tb5, target.getArray()[0]); + assertEquals(tb4, target.getArray()[1]); + assertEquals(tb3, (target.getList().get(0))); + assertEquals(tb2, (target.getList().get(1))); + assertEquals(tb0, (target.getList().get(2))); + assertEquals(null, (target.getList().get(3))); + assertEquals(tb1, (target.getList().get(4))); + assertEquals(tb1, (target.getMap().get("key1"))); + assertEquals(tb0, (target.getMap().get("key2"))); + assertEquals(tb4, (target.getMap().get("key5"))); + assertEquals(tb5, (target.getMap().get("key9"))); + assertEquals(tb5, accessor.getPropertyValue("array[0]")); + assertEquals(tb4, accessor.getPropertyValue("array[1]")); + assertEquals(tb3, accessor.getPropertyValue("list[0]")); + assertEquals(tb2, accessor.getPropertyValue("list[1]")); + assertEquals(tb0, accessor.getPropertyValue("list[2]")); + assertEquals(null, accessor.getPropertyValue("list[3]")); + assertEquals(tb1, accessor.getPropertyValue("list[4]")); + assertEquals(tb1, accessor.getPropertyValue("map[\"key1\"]")); + assertEquals(tb0, accessor.getPropertyValue("map['key2']")); + assertEquals(tb4, accessor.getPropertyValue("map[\"key5\"]")); + assertEquals(tb5, accessor.getPropertyValue("map['key9']")); + } @Test public void propertyType() { - Person person = createPerson("John", "Paris", "FR"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + Person target = createPerson("John", "Paris", "FR"); + AbstractPropertyAccessor accessor = createAccessor(target); assertEquals(String.class, accessor.getPropertyType("address.city")); } @Test - public void propertyTypeUnknownField() { - Simple simple = new Simple("John", 2); - ConfigurablePropertyAccessor accessor = createAccessor(simple); + public void propertyTypeUnknownProperty() { + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyType("foo"), is(nullValue())); } @Test public void propertyTypeDescriptor() { - Person person = createPerson("John", "Paris", "FR"); - ConfigurablePropertyAccessor accessor = createAccessor(person); + Person target = createPerson("John", "Paris", "FR"); + AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyTypeDescriptor("address.city"), is(notNullValue())); } @Test - public void propertyTypeDescriptorUnknownField() { - Simple simple = new Simple("John", 2); - ConfigurablePropertyAccessor accessor = createAccessor(simple); + public void propertyTypeDescriptorUnknownProperty() { + Simple target = new Simple("John", 2); + AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyTypeDescriptor("foo"), is(nullValue())); } + @Test + public void propertyTypeIndexedProperty() { + IndexedTestBean target = new IndexedTestBean(); + AbstractPropertyAccessor accessor = createAccessor(target); + assertEquals(null, accessor.getPropertyType("map[key0]")); + + accessor = createAccessor(target); + accessor.setPropertyValue("map[key0]", "my String"); + assertEquals(String.class, accessor.getPropertyType("map[key0]")); + + accessor = createAccessor(target); + accessor.registerCustomEditor(String.class, "map[key0]", new StringTrimmerEditor(false)); + assertEquals(String.class, accessor.getPropertyType("map[key0]")); + } + + @Test + public void cornerSpr10115() { + Spr10115Bean target = new Spr10115Bean(); + AbstractPropertyAccessor accessor = createAccessor(target); + accessor.setPropertyValue("prop1", "val1"); + assertEquals("val1", Spr10115Bean.prop1); + } + private Person createPerson(String name, String city, String country) { return new Person(name, new Address(city, country)); @@ -397,4 +1869,369 @@ public abstract class AbstractConfigurablePropertyAccessorTests { public void setAge(int age) { } } + + @SuppressWarnings("unused") + private static class Foo { + + private List list; + + private List listOfMaps; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public List getListOfMaps() { + return listOfMaps; + } + + public void setListOfMaps(List listOfMaps) { + this.listOfMaps = listOfMaps; + } + } + + + @SuppressWarnings("unused") + private static class EnumTester { + + private Autowire autowire; + + public void setAutowire(Autowire autowire) { + this.autowire = autowire; + } + + public Autowire getAutowire() { + return autowire; + } + } + + @SuppressWarnings("unused") + private static class PropsTester { + + private Properties properties; + + private String name; + + private String[] stringArray; + + private int[] intArray; + + public void setProperties(Properties p) { + properties = p; + } + + public void setName(String name) { + this.name = name; + } + + public void setStringArray(String[] sa) { + this.stringArray = sa; + } + + public void setIntArray(int[] intArray) { + this.intArray = intArray; + } + } + + @SuppressWarnings("unused") + private static class StringArrayBean { + + private String[] array; + + public String[] getArray() { + return array; + } + + public void setArray(String[] array) { + this.array = array; + } + } + + + @SuppressWarnings("unused") + private static class PrimitiveArrayBean { + + private int[] array; + + public int[] getArray() { + return array; + } + + public void setArray(int[] array) { + this.array = array; + } + } + + @SuppressWarnings("unused") + private static class Employee extends TestBean { + + private String company; + + public String getCompany() { + return company; + } + + public void setCompany(String co) { + this.company = co; + } + } + + @SuppressWarnings("unused") + private static class DifferentTestBean extends TestBean { + // class to test naming of beans in a error message + } + + + @SuppressWarnings("unused") + private static class NumberPropertyBean { + + private byte myPrimitiveByte; + + private Byte myByte; + + private short myPrimitiveShort; + + private Short myShort; + + private int myPrimitiveInt; + + private Integer myInteger; + + private long myPrimitiveLong; + + private Long myLong; + + private float myPrimitiveFloat; + + private Float myFloat; + + private double myPrimitiveDouble; + + private Double myDouble; + + public byte getMyPrimitiveByte() { + return myPrimitiveByte; + } + + public void setMyPrimitiveByte(byte myPrimitiveByte) { + this.myPrimitiveByte = myPrimitiveByte; + } + + public Byte getMyByte() { + return myByte; + } + + public void setMyByte(Byte myByte) { + this.myByte = myByte; + } + + public short getMyPrimitiveShort() { + return myPrimitiveShort; + } + + public void setMyPrimitiveShort(short myPrimitiveShort) { + this.myPrimitiveShort = myPrimitiveShort; + } + + public Short getMyShort() { + return myShort; + } + + public void setMyShort(Short myShort) { + this.myShort = myShort; + } + + public int getMyPrimitiveInt() { + return myPrimitiveInt; + } + + public void setMyPrimitiveInt(int myPrimitiveInt) { + this.myPrimitiveInt = myPrimitiveInt; + } + + public Integer getMyInteger() { + return myInteger; + } + + public void setMyInteger(Integer myInteger) { + this.myInteger = myInteger; + } + + public long getMyPrimitiveLong() { + return myPrimitiveLong; + } + + public void setMyPrimitiveLong(long myPrimitiveLong) { + this.myPrimitiveLong = myPrimitiveLong; + } + + public Long getMyLong() { + return myLong; + } + + public void setMyLong(Long myLong) { + this.myLong = myLong; + } + + public float getMyPrimitiveFloat() { + return myPrimitiveFloat; + } + + public void setMyPrimitiveFloat(float myPrimitiveFloat) { + this.myPrimitiveFloat = myPrimitiveFloat; + } + + public Float getMyFloat() { + return myFloat; + } + + public void setMyFloat(Float myFloat) { + this.myFloat = myFloat; + } + + public double getMyPrimitiveDouble() { + return myPrimitiveDouble; + } + + public void setMyPrimitiveDouble(double myPrimitiveDouble) { + this.myPrimitiveDouble = myPrimitiveDouble; + } + + public Double getMyDouble() { + return myDouble; + } + + public void setMyDouble(Double myDouble) { + this.myDouble = myDouble; + } + } + + + public static class EnumConsumer { + + private Enum enumValue; + + public Enum getEnumValue() { + return enumValue; + } + + public void setEnumValue(Enum enumValue) { + this.enumValue = enumValue; + } + } + + + public static class WildcardEnumConsumer { + + private Enum enumValue; + + public Enum getEnumValue() { + return enumValue; + } + + public void setEnumValue(Enum enumValue) { + this.enumValue = enumValue; + } + } + + + public enum TestEnum { + + TEST_VALUE + } + + + public static class ArrayToObject { + + private Object object; + + public void setObject(Object object) { + this.object = object; + } + + public Object getObject() { + return object; + } + } + + + public static class SkipReaderStub { + + public T[] items; + + public SkipReaderStub() { + } + + public SkipReaderStub(T... items) { + this.items = items; + } + + public void setItems(T... items) { + this.items = items; + } + } + + + static class Spr10115Bean { + + private static String prop1; + + public static void setProp1(String prop1) { + Spr10115Bean.prop1 = prop1; + } + } + + @SuppressWarnings("serial") + public static class ReadOnlyMap extends HashMap { + + private boolean frozen = false; + + private boolean accessed = false; + + public ReadOnlyMap() { + this.frozen = true; + } + + public ReadOnlyMap(Map map) { + super(map); + this.frozen = true; + } + + @Override + public V put(K key, V value) { + if (this.frozen) { + throw new UnsupportedOperationException(); + } + else { + return super.put(key, value); + } + } + + @Override + public Set> entrySet() { + this.accessed = true; + return super.entrySet(); + } + + @Override + public Set keySet() { + this.accessed = true; + return super.keySet(); + } + + @Override + public int size() { + this.accessed = true; + return super.size(); + } + + public boolean isAccessed() { + return this.accessed; + } + } + } 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 ccd56d0e46..cd29de7aef 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -16,1492 +16,92 @@ package org.springframework.beans; -import java.beans.PropertyEditorSupport; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Properties; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; -import org.apache.commons.logging.LogFactory; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.beans.propertyeditors.CustomNumberEditor; -import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; -import org.springframework.beans.propertyeditors.StringTrimmerEditor; -import org.springframework.beans.support.DerivedFromProtectedBaseBean; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.tests.Assume; -import org.springframework.tests.TestGroup; -import org.springframework.tests.sample.beans.BooleanTestBean; -import org.springframework.tests.sample.beans.ITestBean; -import org.springframework.tests.sample.beans.IndexedTestBean; -import org.springframework.tests.sample.beans.NumberTestBean; import org.springframework.tests.sample.beans.TestBean; -import org.springframework.util.StopWatch; -import org.springframework.util.StringUtils; -import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** + * Specific {@link BeanWrapperImpl} tests. + * * @author Rod Johnson * @author Juergen Hoeller * @author Alef Arendsen * @author Arjen Poutsma - * @author Chris Beams - * @author Dave Syer - */ -public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessorTests { - - @Override - protected ConfigurablePropertyAccessor createAccessor(Object target) { - return new BeanWrapperImpl(target); - } - - @Test - public void testNullNestedTypeDescriptor() { - Foo foo = new Foo(); - BeanWrapperImpl wrapper = new BeanWrapperImpl(foo); - wrapper.setConversionService(new DefaultConversionService()); - wrapper.setAutoGrowNestedPaths(true); - wrapper.setPropertyValue("listOfMaps[0]['luckyNumber']", "9"); - assertEquals("9", foo.listOfMaps.get(0).get("luckyNumber")); - } - - @Test - public void testNullNestedTypeDescriptor2() { - Foo foo = new Foo(); - BeanWrapperImpl wrapper = new BeanWrapperImpl(foo); - wrapper.setConversionService(new DefaultConversionService()); - wrapper.setAutoGrowNestedPaths(true); - Map map = new HashMap(); - map.put("favoriteNumber", "9"); - wrapper.setPropertyValue("list[0]", map); - assertEquals(map, foo.list.get(0)); - } - - @Test - public void testNullNestedTypeDescriptorWithNoConversionService() { - Foo foo = new Foo(); - BeanWrapperImpl wrapper = new BeanWrapperImpl(foo); - wrapper.setAutoGrowNestedPaths(true); - wrapper.setPropertyValue("listOfMaps[0]['luckyNumber']", "9"); - assertEquals("9", foo.listOfMaps.get(0).get("luckyNumber")); - } - - @Test - public void testNullNestedTypeDescriptorWithBadConversionService() { - Foo foo = new Foo(); - BeanWrapperImpl wrapper = new BeanWrapperImpl(foo); - wrapper.setConversionService(new GenericConversionService() { - @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - throw new ConversionFailedException(sourceType, targetType, source, null); - } - }); - wrapper.setAutoGrowNestedPaths(true); - wrapper.setPropertyValue("listOfMaps[0]['luckyNumber']", "9"); - assertEquals("9", foo.listOfMaps.get(0).get("luckyNumber")); - } - - - @Test - public void testReadableAndWritableForIndexedProperties() { - BeanWrapper bw = new BeanWrapperImpl(IndexedTestBean.class); - - assertTrue(bw.isReadableProperty("array")); - assertTrue(bw.isReadableProperty("list")); - assertTrue(bw.isReadableProperty("set")); - assertTrue(bw.isReadableProperty("map")); - assertFalse(bw.isReadableProperty("xxx")); - - assertTrue(bw.isWritableProperty("array")); - assertTrue(bw.isWritableProperty("list")); - assertTrue(bw.isWritableProperty("set")); - assertTrue(bw.isWritableProperty("map")); - assertFalse(bw.isWritableProperty("xxx")); - - assertTrue(bw.isReadableProperty("array[0]")); - assertTrue(bw.isReadableProperty("array[0].name")); - assertTrue(bw.isReadableProperty("list[0]")); - assertTrue(bw.isReadableProperty("list[0].name")); - assertTrue(bw.isReadableProperty("set[0]")); - assertTrue(bw.isReadableProperty("set[0].name")); - assertTrue(bw.isReadableProperty("map[key1]")); - assertTrue(bw.isReadableProperty("map[key1].name")); - assertTrue(bw.isReadableProperty("map[key4][0]")); - assertTrue(bw.isReadableProperty("map[key4][0].name")); - assertTrue(bw.isReadableProperty("map[key4][1]")); - assertTrue(bw.isReadableProperty("map[key4][1].name")); - assertFalse(bw.isReadableProperty("array[key1]")); - - assertTrue(bw.isWritableProperty("array[0]")); - assertTrue(bw.isWritableProperty("array[0].name")); - assertTrue(bw.isWritableProperty("list[0]")); - assertTrue(bw.isWritableProperty("list[0].name")); - assertTrue(bw.isWritableProperty("set[0]")); - assertTrue(bw.isWritableProperty("set[0].name")); - assertTrue(bw.isWritableProperty("map[key1]")); - assertTrue(bw.isWritableProperty("map[key1].name")); - assertTrue(bw.isWritableProperty("map[key4][0]")); - assertTrue(bw.isWritableProperty("map[key4][0].name")); - assertTrue(bw.isWritableProperty("map[key4][1]")); - assertTrue(bw.isWritableProperty("map[key4][1].name")); - assertFalse(bw.isWritableProperty("array[key1]")); - } - - @Test - public void testTypeDeterminationForIndexedProperty() { - BeanWrapper bw = new BeanWrapperImpl(IndexedTestBean.class); - assertEquals(null, bw.getPropertyType("map[key0]")); - - bw = new BeanWrapperImpl(IndexedTestBean.class); - bw.setPropertyValue("map[key0]", "my String"); - assertEquals(String.class, bw.getPropertyType("map[key0]")); - - bw = new BeanWrapperImpl(IndexedTestBean.class); - bw.registerCustomEditor(String.class, "map[key0]", new StringTrimmerEditor(false)); - assertEquals(String.class, bw.getPropertyType("map[key0]")); - } - - @Test - public void testGetterThrowsException() { - GetterBean gb = new GetterBean(); - BeanWrapper bw = new BeanWrapperImpl(gb); - bw.setPropertyValue("name", "tom"); - assertTrue("Set name to tom", gb.getName().equals("tom")); - } - - @Test - public void testEmptyPropertyValuesSet() { - TestBean t = new TestBean(); - int age = 50; - String name = "Tony"; - t.setAge(age); - t.setName(name); - try { - BeanWrapper bw = new BeanWrapperImpl(t); - assertTrue("age is OK", t.getAge() == age); - assertTrue("name is OK", name.equals(t.getName())); - bw.setPropertyValues(new MutablePropertyValues()); - // Check its unchanged - assertTrue("age is OK", t.getAge() == age); - assertTrue("name is OK", name.equals(t.getName())); - } - catch (BeansException ex) { - fail("Shouldn't throw exception when everything is valid"); - } - } - - @Test - public void testAllValid() { - TestBean t = new TestBean(); - String newName = "tony"; - int newAge = 65; - String newTouchy = "valid"; - try { - BeanWrapper bw = new BeanWrapperImpl(t); - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.addPropertyValue(new PropertyValue("age", new Integer(newAge))); - pvs.addPropertyValue(new PropertyValue("name", newName)); - - pvs.addPropertyValue(new PropertyValue("touchy", newTouchy)); - bw.setPropertyValues(pvs); - assertTrue("Validly set property must stick", t.getName().equals(newName)); - assertTrue("Validly set property must stick", t.getTouchy().equals(newTouchy)); - assertTrue("Validly set property must stick", t.getAge() == newAge); - } - catch (BeansException ex) { - fail("Shouldn't throw exception when everything is valid"); - } - } - - @Test - public void testBeanWrapperUpdates() { - TestBean t = new TestBean(); - int newAge = 33; - try { - BeanWrapper bw = new BeanWrapperImpl(t); - t.setAge(newAge); - Object bwAge = bw.getPropertyValue("age"); - assertTrue("Age is an integer", bwAge instanceof Integer); - int bwi = ((Integer) bwAge).intValue(); - assertTrue("Bean wrapper must pick up changes", bwi == newAge); - } - catch (Exception ex) { - fail("Shouldn't throw exception when everything is valid"); - } - } - - @Test - public void testValidNullUpdate() { - TestBean t = new TestBean(); - t.setName("Frank"); // we need to change it back - t.setSpouse(t); - BeanWrapper bw = new BeanWrapperImpl(t); - assertTrue("name is not null to start off", t.getName() != null); - bw.setPropertyValue("name", null); - assertTrue("name is now null", t.getName() == null); - // now test with non-string - assertTrue("spouse is not null to start off", t.getSpouse() != null); - bw.setPropertyValue("spouse", null); - assertTrue("spouse is now null", t.getSpouse() == null); - } - - @Test - public void testIgnoringIndexedProperty() { - MutablePropertyValues values = new MutablePropertyValues(); - values.add("toBeIgnored[0]", new Integer(42)); - BeanWrapper bw = new BeanWrapperImpl(new Object()); - bw.setPropertyValues(values, true); - } - - @Test - public void testConvertPrimitiveToString() { - MutablePropertyValues values = new MutablePropertyValues(); - values.add("name", new Integer(42)); - TestBean tb = new TestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.setPropertyValues(values); - assertEquals("42", tb.getName()); - } - - @Test - public void testConvertClassToString() { - MutablePropertyValues values = new MutablePropertyValues(); - values.add("name", Integer.class); - TestBean tb = new TestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.registerCustomEditor(String.class, new PropertyEditorSupport() { - @Override - public void setValue(Object value) { - super.setValue(value.toString()); - } - }); - bw.setPropertyValues(values); - assertEquals(Integer.class.toString(), tb.getName()); - } - - @Test - public void testBooleanObject() { - BooleanTestBean tb = new BooleanTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - - bw.setPropertyValue("bool2", "true"); - assertTrue("Correct bool2 value", Boolean.TRUE.equals(bw.getPropertyValue("bool2"))); - assertTrue("Correct bool2 value", tb.getBool2().booleanValue()); - - bw.setPropertyValue("bool2", "false"); - assertTrue("Correct bool2 value", Boolean.FALSE.equals(bw.getPropertyValue("bool2"))); - assertTrue("Correct bool2 value", !tb.getBool2().booleanValue()); - - } - - @Test - public void testNumberObjects() { - NumberTestBean tb = new NumberTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - - try { - bw.setPropertyValue("short2", "2"); - bw.setPropertyValue("int2", "8"); - bw.setPropertyValue("long2", "6"); - bw.setPropertyValue("bigInteger", "3"); - bw.setPropertyValue("float2", "8.1"); - bw.setPropertyValue("double2", "6.1"); - bw.setPropertyValue("bigDecimal", "4.0"); - } - catch (BeansException ex) { - fail("Should not throw BeansException: " + ex.getMessage()); - } - - assertTrue("Correct short2 value", new Short("2").equals(bw.getPropertyValue("short2"))); - assertTrue("Correct short2 value", new Short("2").equals(tb.getShort2())); - assertTrue("Correct int2 value", new Integer("8").equals(bw.getPropertyValue("int2"))); - assertTrue("Correct int2 value", new Integer("8").equals(tb.getInt2())); - assertTrue("Correct long2 value", new Long("6").equals(bw.getPropertyValue("long2"))); - assertTrue("Correct long2 value", new Long("6").equals(tb.getLong2())); - assertTrue("Correct bigInteger value", new BigInteger("3").equals(bw.getPropertyValue("bigInteger"))); - assertTrue("Correct bigInteger value", new BigInteger("3").equals(tb.getBigInteger())); - assertTrue("Correct float2 value", new Float("8.1").equals(bw.getPropertyValue("float2"))); - assertTrue("Correct float2 value", new Float("8.1").equals(tb.getFloat2())); - assertTrue("Correct double2 value", new Double("6.1").equals(bw.getPropertyValue("double2"))); - assertTrue("Correct double2 value", new Double("6.1").equals(tb.getDouble2())); - assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(bw.getPropertyValue("bigDecimal"))); - assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(tb.getBigDecimal())); - } - - @Test - public void testNumberCoercion() { - NumberTestBean tb = new NumberTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - - try { - bw.setPropertyValue("short2", new Integer(2)); - bw.setPropertyValue("int2", new Long(8)); - bw.setPropertyValue("long2", new BigInteger("6")); - bw.setPropertyValue("bigInteger", new Integer(3)); - bw.setPropertyValue("float2", new Double(8.1)); - bw.setPropertyValue("double2", new BigDecimal(6.1)); - bw.setPropertyValue("bigDecimal", new Float(4.0)); - } - catch (BeansException ex) { - fail("Should not throw BeansException: " + ex.getMessage()); - } - - assertTrue("Correct short2 value", new Short("2").equals(bw.getPropertyValue("short2"))); - assertTrue("Correct short2 value", new Short("2").equals(tb.getShort2())); - assertTrue("Correct int2 value", new Integer("8").equals(bw.getPropertyValue("int2"))); - assertTrue("Correct int2 value", new Integer("8").equals(tb.getInt2())); - assertTrue("Correct long2 value", new Long("6").equals(bw.getPropertyValue("long2"))); - assertTrue("Correct long2 value", new Long("6").equals(tb.getLong2())); - assertTrue("Correct bigInteger value", new BigInteger("3").equals(bw.getPropertyValue("bigInteger"))); - assertTrue("Correct bigInteger value", new BigInteger("3").equals(tb.getBigInteger())); - assertTrue("Correct float2 value", new Float("8.1").equals(bw.getPropertyValue("float2"))); - assertTrue("Correct float2 value", new Float("8.1").equals(tb.getFloat2())); - assertTrue("Correct double2 value", new Double("6.1").equals(bw.getPropertyValue("double2"))); - assertTrue("Correct double2 value", new Double("6.1").equals(tb.getDouble2())); - assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(bw.getPropertyValue("bigDecimal"))); - assertTrue("Correct bigDecimal value", new BigDecimal("4.0").equals(tb.getBigDecimal())); - } - - @Test - public void testEnumByFieldName() { - EnumTester et = new EnumTester(); - BeanWrapper bw = new BeanWrapperImpl(et); - - bw.setPropertyValue("autowire", "BY_NAME"); - assertEquals(Autowire.BY_NAME, et.getAutowire()); - - bw.setPropertyValue("autowire", " BY_TYPE "); - assertEquals(Autowire.BY_TYPE, et.getAutowire()); - - try { - bw.setPropertyValue("autowire", "NHERITED"); - fail("Should have thrown TypeMismatchException"); - } - catch (TypeMismatchException ex) { - // expected - } - } - - @Test - public void testPropertiesProperty() throws Exception { - PropsTester pt = new PropsTester(); - BeanWrapper bw = new BeanWrapperImpl(pt); - bw.setPropertyValue("name", "ptest"); - - // Note format... - String ps = "peace=war\nfreedom=slavery"; - bw.setPropertyValue("properties", ps); - - assertTrue("name was set", pt.name.equals("ptest")); - assertTrue("props non null", pt.props != null); - String freedomVal = pt.props.getProperty("freedom"); - String peaceVal = pt.props.getProperty("peace"); - assertTrue("peace==war", peaceVal.equals("war")); - assertTrue("Freedom==slavery", freedomVal.equals("slavery")); - } - - @Test - public void testStringArrayProperty() throws Exception { - PropsTester pt = new PropsTester(); - BeanWrapper bw = new BeanWrapperImpl(pt); - - bw.setPropertyValue("stringArray", new String[] {"foo", "fi", "fi", "fum"}); - assertTrue("stringArray length = 4", pt.stringArray.length == 4); - assertTrue("correct values", pt.stringArray[0].equals("foo") && pt.stringArray[1].equals("fi") && - pt.stringArray[2].equals("fi") && pt.stringArray[3].equals("fum")); - - List list = new ArrayList(); - list.add("foo"); - list.add("fi"); - list.add("fi"); - list.add("fum"); - bw.setPropertyValue("stringArray", list); - assertTrue("stringArray length = 4", pt.stringArray.length == 4); - assertTrue("correct values", pt.stringArray[0].equals("foo") && pt.stringArray[1].equals("fi") && - pt.stringArray[2].equals("fi") && pt.stringArray[3].equals("fum")); - - Set set = new HashSet(); - set.add("foo"); - set.add("fi"); - set.add("fum"); - bw.setPropertyValue("stringArray", set); - assertTrue("stringArray length = 3", pt.stringArray.length == 3); - List result = Arrays.asList(pt.stringArray); - assertTrue("correct values", result.contains("foo") && result.contains("fi") && result.contains("fum")); - - bw.setPropertyValue("stringArray", "one"); - assertTrue("stringArray length = 1", pt.stringArray.length == 1); - assertTrue("stringArray elt is ok", pt.stringArray[0].equals("one")); - - bw.setPropertyValue("stringArray", null); - assertTrue("stringArray is null", pt.stringArray == null); - } - - @Test - public void testStringArrayPropertyWithCustomStringEditor() throws Exception { - PropsTester pt = new PropsTester(); - BeanWrapper bw = new BeanWrapperImpl(pt); - bw.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() { - @Override - public void setAsText(String text) { - setValue(text.substring(1)); - } - }); - - bw.setPropertyValue("stringArray", new String[] {"4foo", "7fi", "6fi", "5fum"}); - assertTrue("stringArray length = 4", pt.stringArray.length == 4); - assertTrue("correct values", pt.stringArray[0].equals("foo") && pt.stringArray[1].equals("fi") && - pt.stringArray[2].equals("fi") && pt.stringArray[3].equals("fum")); - - List list = new ArrayList(); - list.add("4foo"); - list.add("7fi"); - list.add("6fi"); - list.add("5fum"); - bw.setPropertyValue("stringArray", list); - assertTrue("stringArray length = 4", pt.stringArray.length == 4); - assertTrue("correct values", pt.stringArray[0].equals("foo") && pt.stringArray[1].equals("fi") && - pt.stringArray[2].equals("fi") && pt.stringArray[3].equals("fum")); - - Set set = new HashSet(); - set.add("4foo"); - set.add("7fi"); - set.add("6fum"); - bw.setPropertyValue("stringArray", set); - assertTrue("stringArray length = 3", pt.stringArray.length == 3); - List result = Arrays.asList(pt.stringArray); - assertTrue("correct values", result.contains("foo") && result.contains("fi") && result.contains("fum")); - - bw.setPropertyValue("stringArray", "8one"); - assertTrue("stringArray length = 1", pt.stringArray.length == 1); - assertTrue("correct values", pt.stringArray[0].equals("one")); - } - - @Test - public void testStringArrayPropertyWithStringSplitting() throws Exception { - PropsTester pt = new PropsTester(); - BeanWrapperImpl bw = new BeanWrapperImpl(pt); - bw.useConfigValueEditors(); - bw.setPropertyValue("stringArray", "a1,b2"); - assertTrue("stringArray length = 2", pt.stringArray.length == 2); - assertTrue("correct values", pt.stringArray[0].equals("a1") && pt.stringArray[1].equals("b2")); - } - - @Test - public void testStringArrayPropertyWithCustomStringDelimiter() throws Exception { - PropsTester pt = new PropsTester(); - BeanWrapper bw = new BeanWrapperImpl(pt); - bw.registerCustomEditor(String[].class, "stringArray", new StringArrayPropertyEditor("-")); - bw.setPropertyValue("stringArray", "a1-b2"); - assertTrue("stringArray length = 2", pt.stringArray.length == 2); - assertTrue("correct values", pt.stringArray[0].equals("a1") && pt.stringArray[1].equals("b2")); - } - - @Test - public void testStringArrayAutoGrow() throws Exception { - StringArrayBean target = new StringArrayBean(); - BeanWrapper bw = new BeanWrapperImpl(target); - bw.setAutoGrowNestedPaths(true); - - bw.setPropertyValue("array[0]", "Test0"); - assertEquals(1, target.getArray().length); - - bw.setPropertyValue("array[2]", "Test2"); - assertEquals(3, target.getArray().length); - assertTrue("correct values", target.getArray()[0].equals("Test0") && target.getArray()[1] == null && - target.getArray()[2].equals("Test2")); - } - - @Test - public void testPrimitiveArrayAutoGrow() throws Exception { - PrimitiveArrayBean target = new PrimitiveArrayBean(); - BeanWrapper bw = new BeanWrapperImpl(target); - bw.setAutoGrowNestedPaths(true); - - bw.setPropertyValue("array[0]", 1); - assertEquals(1, target.getArray().length); - - bw.setPropertyValue("array[2]", 3); - assertEquals(3, target.getArray().length); - assertTrue("correct values", target.getArray()[0] == 1 && target.getArray()[1] == 0 && - target.getArray()[2] == 3); - } - - @Test - public void testStringPropertyWithCustomEditor() throws Exception { - TestBean tb = new TestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.registerCustomEditor(String.class, "name", new PropertyEditorSupport() { - @Override - public void setValue(Object value) { - if (value instanceof String[]) { - setValue(StringUtils.arrayToDelimitedString(((String[]) value), "-")); - } - else { - super.setValue(value != null ? value : ""); - } - } - }); - bw.setPropertyValue("name", new String[] {}); - assertEquals("", tb.getName()); - bw.setPropertyValue("name", new String[] {"a1", "b2"}); - assertEquals("a1-b2", tb.getName()); - bw.setPropertyValue("name", null); - assertEquals("", tb.getName()); - } - - @Test - public void testIntArrayProperty() { - PropsTester pt = new PropsTester(); - BeanWrapper bw = new BeanWrapperImpl(pt); - - bw.setPropertyValue("intArray", new int[] {4, 5, 2, 3}); - assertTrue("intArray length = 4", pt.intArray.length == 4); - assertTrue("correct values", pt.intArray[0] == 4 && pt.intArray[1] == 5 && - pt.intArray[2] == 2 && pt.intArray[3] == 3); - - bw.setPropertyValue("intArray", new String[] {"4", "5", "2", "3"}); - assertTrue("intArray length = 4", pt.intArray.length == 4); - assertTrue("correct values", pt.intArray[0] == 4 && pt.intArray[1] == 5 && - pt.intArray[2] == 2 && pt.intArray[3] == 3); - - List list = new ArrayList(); - list.add(new Integer(4)); - list.add("5"); - list.add(new Integer(2)); - list.add("3"); - bw.setPropertyValue("intArray", list); - assertTrue("intArray length = 4", pt.intArray.length == 4); - assertTrue("correct values", pt.intArray[0] == 4 && pt.intArray[1] == 5 && - pt.intArray[2] == 2 && pt.intArray[3] == 3); - - Set set = new HashSet(); - set.add("4"); - set.add(new Integer(5)); - set.add("3"); - bw.setPropertyValue("intArray", set); - assertTrue("intArray length = 3", pt.intArray.length == 3); - List result = new ArrayList(); - result.add(new Integer(pt.intArray[0])); - result.add(new Integer(pt.intArray[1])); - result.add(new Integer(pt.intArray[2])); - assertTrue("correct values", result.contains(new Integer(4)) && result.contains(new Integer(5)) && - result.contains(new Integer(3))); - - bw.setPropertyValue("intArray", new Integer[] {new Integer(1)}); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - - bw.setPropertyValue("intArray", new Integer(1)); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - - bw.setPropertyValue("intArray", new String[] {"1"}); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - - bw.setPropertyValue("intArray", "1"); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - } - - @Test - public void testIntArrayPropertyWithCustomEditor() { - PropsTester pt = new PropsTester(); - BeanWrapper bw = new BeanWrapperImpl(pt); - bw.registerCustomEditor(int.class, new PropertyEditorSupport() { - @Override - public void setAsText(String text) { - setValue(new Integer(Integer.parseInt(text) + 1)); - } - }); - - bw.setPropertyValue("intArray", new int[] {4, 5, 2, 3}); - assertTrue("intArray length = 4", pt.intArray.length == 4); - assertTrue("correct values", pt.intArray[0] == 4 && pt.intArray[1] == 5 && - pt.intArray[2] == 2 && pt.intArray[3] == 3); - - bw.setPropertyValue("intArray", new String[] {"3", "4", "1", "2"}); - assertTrue("intArray length = 4", pt.intArray.length == 4); - assertTrue("correct values", pt.intArray[0] == 4 && pt.intArray[1] == 5 && - pt.intArray[2] == 2 && pt.intArray[3] == 3); - - bw.setPropertyValue("intArray", new Integer(1)); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - - bw.setPropertyValue("intArray", new String[] {"0"}); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - - bw.setPropertyValue("intArray", "0"); - assertTrue("intArray length = 4", pt.intArray.length == 1); - assertTrue("correct values", pt.intArray[0] == 1); - } - - @Test - public void testIntArrayPropertyWithStringSplitting() throws Exception { - PropsTester pt = new PropsTester(); - BeanWrapperImpl bw = new BeanWrapperImpl(pt); - bw.useConfigValueEditors(); - bw.setPropertyValue("intArray", "4,5"); - assertTrue("intArray length = 2", pt.intArray.length == 2); - assertTrue("correct values", pt.intArray[0] == 4 && pt.intArray[1] == 5); - } - - @Test - public void testIndividualAllValid() { - TestBean t = new TestBean(); - String newName = "tony"; - int newAge = 65; - String newTouchy = "valid"; - try { - BeanWrapper bw = new BeanWrapperImpl(t); - bw.setPropertyValue("age", new Integer(newAge)); - bw.setPropertyValue(new PropertyValue("name", newName)); - bw.setPropertyValue(new PropertyValue("touchy", newTouchy)); - assertTrue("Validly set property must stick", t.getName().equals(newName)); - assertTrue("Validly set property must stick", t.getTouchy().equals(newTouchy)); - assertTrue("Validly set property must stick", t.getAge() == newAge); - } - catch (BeansException ex) { - fail("Shouldn't throw exception when everything is valid"); - } - } - - @Test - public void test2Invalid() { - TestBean t = new TestBean(); - String newName = "tony"; - String invalidTouchy = ".valid"; - try { - BeanWrapper bw = new BeanWrapperImpl(t); - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.addPropertyValue(new PropertyValue("age", "foobar")); - pvs.addPropertyValue(new PropertyValue("name", newName)); - pvs.addPropertyValue(new PropertyValue("touchy", invalidTouchy)); - bw.setPropertyValues(pvs); - fail("Should throw exception when everything is valid"); - } - catch (PropertyBatchUpdateException ex) { - assertTrue("Must contain 2 exceptions", ex.getExceptionCount() == 2); - // Test validly set property matches - assertTrue("Validly set property must stick", t.getName().equals(newName)); - assertTrue("Invalidly set property must retain old value", t.getAge() == 0); - assertTrue("New value of dodgy setter must be available through exception", - ex.getPropertyAccessException("touchy").getPropertyChangeEvent().getNewValue().equals(invalidTouchy)); - } - } - - @Test - public void testPossibleMatches() { - TestBean tb = new TestBean(); - try { - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.setPropertyValue("ag", "foobar"); - fail("Should throw exception on invalid property"); - } - catch (NotWritablePropertyException ex) { - // expected - assertEquals(1, ex.getPossibleMatches().length); - assertEquals("age", ex.getPossibleMatches()[0]); - } - } - - @Test - public void testTypeMismatch() { - TestBean t = new TestBean(); - try { - BeanWrapper bw = new BeanWrapperImpl(t); - bw.setPropertyValue("age", "foobar"); - fail("Should throw exception on type mismatch"); - } - catch (TypeMismatchException ex) { - // expected - } - } - - @Test - public void testEmptyValueForPrimitiveProperty() { - TestBean t = new TestBean(); - try { - BeanWrapper bw = new BeanWrapperImpl(t); - bw.setPropertyValue("age", ""); - fail("Should throw exception on type mismatch"); - } - catch (TypeMismatchException ex) { - // expected - } - catch (Exception ex) { - fail("Shouldn't throw exception other than Type mismatch"); - } - } - - @Test - public void testSetPropertyValuesIgnoresInvalidNestedOnRequest() { - ITestBean rod = new TestBean(); - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.addPropertyValue(new PropertyValue("name", "rod")); - pvs.addPropertyValue(new PropertyValue("graceful.rubbish", "tony")); - pvs.addPropertyValue(new PropertyValue("more.garbage", new Object())); - BeanWrapper bw = new BeanWrapperImpl(rod); - bw.setPropertyValues(pvs, true); - assertTrue("Set valid and ignored invalid", rod.getName().equals("rod")); - try { - // Don't ignore: should fail - bw.setPropertyValues(pvs, false); - fail("Shouldn't have ignored invalid updates"); - } - catch (NotWritablePropertyException ex) { - // OK: but which exception?? - } - } - - @Test - public void testGetNestedProperty() { - ITestBean rod = new TestBean("rod", 31); - ITestBean kerry = new TestBean("kerry", 35); - rod.setSpouse(kerry); - kerry.setSpouse(rod); - BeanWrapper bw = new BeanWrapperImpl(rod); - Integer KA = (Integer) bw.getPropertyValue("spouse.age"); - assertTrue("kerry is 35", KA.intValue() == 35); - Integer RA = (Integer) bw.getPropertyValue("spouse.spouse.age"); - assertTrue("rod is 31, not" + RA, RA.intValue() == 31); - ITestBean spousesSpouse = (ITestBean) bw.getPropertyValue("spouse.spouse"); - assertTrue("spousesSpouse = initial point", rod == spousesSpouse); - } - - @Test - public void testGetNestedPropertyNullValue() throws Exception { - ITestBean rod = new TestBean("rod", 31); - ITestBean kerry = new TestBean("kerry", 35); - rod.setSpouse(kerry); - - BeanWrapper bw = new BeanWrapperImpl(rod); - try { - bw.getPropertyValue("spouse.spouse.age"); - fail("Shouldn't have succeded with null path"); - } - catch (NullValueInNestedPathException ex) { - // ok - assertTrue("it was the spouse.spouse property that was null, not " + ex.getPropertyName(), - ex.getPropertyName().equals("spouse.spouse")); - } - } - - @Test - public void testSetNestedProperty() throws Exception { - ITestBean rod = new TestBean("rod", 31); - ITestBean kerry = new TestBean("kerry", 0); - - BeanWrapper bw = new BeanWrapperImpl(rod); - bw.setPropertyValue("spouse", kerry); - - assertTrue("nested set worked", rod.getSpouse() == kerry); - assertTrue("no back relation", kerry.getSpouse() == null); - bw.setPropertyValue(new PropertyValue("spouse.spouse", rod)); - assertTrue("nested set worked", kerry.getSpouse() == rod); - assertTrue("kerry age not set", kerry.getAge() == 0); - bw.setPropertyValue(new PropertyValue("spouse.age", new Integer(35))); - assertTrue("Set primitive on spouse", kerry.getAge() == 35); - - assertEquals(kerry, bw.getPropertyValue("spouse")); - assertEquals(rod, bw.getPropertyValue("spouse.spouse")); - } - - @Test - public void testSetNestedPropertyNullValue() throws Exception { - ITestBean rod = new TestBean("rod", 31); - BeanWrapper bw = new BeanWrapperImpl(rod); - try { - bw.setPropertyValue("spouse.age", new Integer(31)); - fail("Shouldn't have succeeded with null path"); - } - catch (NullValueInNestedPathException ex) { - // expected - assertTrue("it was the spouse property that was null, not " + ex.getPropertyName(), - ex.getPropertyName().equals("spouse")); - } - } - - @Test - public void testSetNestedPropertyPolymorphic() throws Exception { - ITestBean rod = new TestBean("rod", 31); - ITestBean kerry = new Employee(); - - BeanWrapper bw = new BeanWrapperImpl(rod); - bw.setPropertyValue("spouse", kerry); - bw.setPropertyValue("spouse.age", new Integer(35)); - bw.setPropertyValue("spouse.name", "Kerry"); - bw.setPropertyValue("spouse.company", "Lewisham"); - assertTrue("kerry name is Kerry", kerry.getName().equals("Kerry")); - - assertTrue("nested set worked", rod.getSpouse() == kerry); - assertTrue("no back relation", kerry.getSpouse() == null); - bw.setPropertyValue(new PropertyValue("spouse.spouse", rod)); - assertTrue("nested set worked", kerry.getSpouse() == rod); - - BeanWrapper kbw = new BeanWrapperImpl(kerry); - assertTrue("spouse.spouse.spouse.spouse.company=Lewisham", - "Lewisham".equals(kbw.getPropertyValue("spouse.spouse.spouse.spouse.company"))); - } - - @Test - public void testNullObject() { - try { - new BeanWrapperImpl((Object) null); - fail("Must throw an exception when constructed with null object"); - } - catch (IllegalArgumentException ex) { - // expected - } - } - - @Test - public void testNestedProperties() { - String doctorCompany = ""; - String lawyerCompany = "Dr. Sueem"; - TestBean tb = new TestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.setPropertyValue("doctor.company", doctorCompany); - bw.setPropertyValue("lawyer.company", lawyerCompany); - assertEquals(doctorCompany, tb.getDoctor().getCompany()); - assertEquals(lawyerCompany, tb.getLawyer().getCompany()); - } - - @Test - public void testIndexedProperties() { - IndexedTestBean bean = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - TestBean tb0 = bean.getArray()[0]; - TestBean tb1 = bean.getArray()[1]; - TestBean tb2 = ((TestBean) bean.getList().get(0)); - TestBean tb3 = ((TestBean) bean.getList().get(1)); - TestBean tb6 = ((TestBean) bean.getSet().toArray()[0]); - TestBean tb7 = ((TestBean) bean.getSet().toArray()[1]); - TestBean tb4 = ((TestBean) bean.getMap().get("key1")); - TestBean tb5 = ((TestBean) bean.getMap().get("key.3")); - assertEquals("name0", tb0.getName()); - assertEquals("name1", tb1.getName()); - assertEquals("name2", tb2.getName()); - assertEquals("name3", tb3.getName()); - assertEquals("name6", tb6.getName()); - assertEquals("name7", tb7.getName()); - assertEquals("name4", tb4.getName()); - assertEquals("name5", tb5.getName()); - assertEquals("name0", bw.getPropertyValue("array[0].name")); - assertEquals("name1", bw.getPropertyValue("array[1].name")); - assertEquals("name2", bw.getPropertyValue("list[0].name")); - assertEquals("name3", bw.getPropertyValue("list[1].name")); - assertEquals("name6", bw.getPropertyValue("set[0].name")); - assertEquals("name7", bw.getPropertyValue("set[1].name")); - assertEquals("name4", bw.getPropertyValue("map[key1].name")); - assertEquals("name5", bw.getPropertyValue("map[key.3].name")); - assertEquals("name4", bw.getPropertyValue("map['key1'].name")); - assertEquals("name5", bw.getPropertyValue("map[\"key.3\"].name")); - assertEquals("nameX", bw.getPropertyValue("map[key4][0].name")); - assertEquals("nameY", bw.getPropertyValue("map[key4][1].name")); - - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("array[0].name", "name5"); - pvs.add("array[1].name", "name4"); - pvs.add("list[0].name", "name3"); - pvs.add("list[1].name", "name2"); - pvs.add("set[0].name", "name8"); - pvs.add("set[1].name", "name9"); - pvs.add("map[key1].name", "name1"); - pvs.add("map['key.3'].name", "name0"); - pvs.add("map[key4][0].name", "nameA"); - pvs.add("map[key4][1].name", "nameB"); - bw.setPropertyValues(pvs); - assertEquals("name5", tb0.getName()); - assertEquals("name4", tb1.getName()); - assertEquals("name3", tb2.getName()); - assertEquals("name2", tb3.getName()); - assertEquals("name1", tb4.getName()); - assertEquals("name0", tb5.getName()); - assertEquals("name5", bw.getPropertyValue("array[0].name")); - assertEquals("name4", bw.getPropertyValue("array[1].name")); - assertEquals("name3", bw.getPropertyValue("list[0].name")); - assertEquals("name2", bw.getPropertyValue("list[1].name")); - assertEquals("name8", bw.getPropertyValue("set[0].name")); - assertEquals("name9", bw.getPropertyValue("set[1].name")); - assertEquals("name1", bw.getPropertyValue("map[\"key1\"].name")); - assertEquals("name0", bw.getPropertyValue("map['key.3'].name")); - assertEquals("nameA", bw.getPropertyValue("map[key4][0].name")); - assertEquals("nameB", bw.getPropertyValue("map[key4][1].name")); - } - - @Test - public void testIndexedPropertiesWithDirectAccess() { - IndexedTestBean bean = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - TestBean tb0 = bean.getArray()[0]; - TestBean tb1 = bean.getArray()[1]; - TestBean tb2 = ((TestBean) bean.getList().get(0)); - TestBean tb3 = ((TestBean) bean.getList().get(1)); - TestBean tb6 = ((TestBean) bean.getSet().toArray()[0]); - TestBean tb7 = ((TestBean) bean.getSet().toArray()[1]); - TestBean tb4 = ((TestBean) bean.getMap().get("key1")); - TestBean tb5 = ((TestBean) bean.getMap().get("key2")); - assertEquals(tb0, bw.getPropertyValue("array[0]")); - assertEquals(tb1, bw.getPropertyValue("array[1]")); - assertEquals(tb2, bw.getPropertyValue("list[0]")); - assertEquals(tb3, bw.getPropertyValue("list[1]")); - assertEquals(tb6, bw.getPropertyValue("set[0]")); - assertEquals(tb7, bw.getPropertyValue("set[1]")); - assertEquals(tb4, bw.getPropertyValue("map[key1]")); - assertEquals(tb5, bw.getPropertyValue("map[key2]")); - assertEquals(tb4, bw.getPropertyValue("map['key1']")); - assertEquals(tb5, bw.getPropertyValue("map[\"key2\"]")); - - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("array[0]", tb5); - pvs.add("array[1]", tb4); - pvs.add("list[0]", tb3); - pvs.add("list[1]", tb2); - pvs.add("list[2]", tb0); - pvs.add("list[4]", tb1); - pvs.add("map[key1]", tb1); - pvs.add("map['key2']", tb0); - pvs.add("map[key5]", tb4); - pvs.add("map['key9']", tb5); - bw.setPropertyValues(pvs); - assertEquals(tb5, bean.getArray()[0]); - assertEquals(tb4, bean.getArray()[1]); - assertEquals(tb3, (bean.getList().get(0))); - assertEquals(tb2, (bean.getList().get(1))); - assertEquals(tb0, (bean.getList().get(2))); - assertEquals(null, (bean.getList().get(3))); - assertEquals(tb1, (bean.getList().get(4))); - assertEquals(tb1, (bean.getMap().get("key1"))); - assertEquals(tb0, (bean.getMap().get("key2"))); - assertEquals(tb4, (bean.getMap().get("key5"))); - assertEquals(tb5, (bean.getMap().get("key9"))); - assertEquals(tb5, bw.getPropertyValue("array[0]")); - assertEquals(tb4, bw.getPropertyValue("array[1]")); - assertEquals(tb3, bw.getPropertyValue("list[0]")); - assertEquals(tb2, bw.getPropertyValue("list[1]")); - assertEquals(tb0, bw.getPropertyValue("list[2]")); - assertEquals(null, bw.getPropertyValue("list[3]")); - assertEquals(tb1, bw.getPropertyValue("list[4]")); - assertEquals(tb1, bw.getPropertyValue("map[\"key1\"]")); - assertEquals(tb0, bw.getPropertyValue("map['key2']")); - assertEquals(tb4, bw.getPropertyValue("map[\"key5\"]")); - assertEquals(tb5, bw.getPropertyValue("map['key9']")); - } - - @Test - public void testMapAccessWithTypeConversion() { - IndexedTestBean bean = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - bw.registerCustomEditor(TestBean.class, new PropertyEditorSupport() { - @Override - public void setAsText(String text) throws IllegalArgumentException { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(); - } - setValue(new TestBean(text)); - } - }); - - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("map[key1]", "rod"); - pvs.add("map[key2]", "rob"); - bw.setPropertyValues(pvs); - assertEquals("rod", ((TestBean) bean.getMap().get("key1")).getName()); - assertEquals("rob", ((TestBean) bean.getMap().get("key2")).getName()); - - pvs = new MutablePropertyValues(); - pvs.add("map[key1]", "rod"); - pvs.add("map[key2]", ""); - try { - bw.setPropertyValues(pvs); - fail("Should have thrown TypeMismatchException"); - } - catch (PropertyBatchUpdateException ex) { - PropertyAccessException pae = ex.getPropertyAccessException("map[key2]"); - assertTrue(pae instanceof TypeMismatchException); - } - } - - @Test - public void testMapAccessWithUnmodifiableMap() { - IndexedTestBean bean = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - bw.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() { - @Override - public void setAsText(String text) throws IllegalArgumentException { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(); - } - setValue(new TestBean(text)); - } - }); - - Map inputMap = new HashMap(); - inputMap.put(new Integer(1), "rod"); - inputMap.put(new Integer(2), "rob"); - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("map", Collections.unmodifiableMap(inputMap)); - bw.setPropertyValues(pvs); - assertEquals("rod", ((TestBean) bean.getMap().get(new Integer(1))).getName()); - assertEquals("rob", ((TestBean) bean.getMap().get(new Integer(2))).getName()); - } - - @Test - public void testMapAccessWithCustomUnmodifiableMap() { - IndexedTestBean bean = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - bw.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() { - @Override - public void setAsText(String text) throws IllegalArgumentException { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(); - } - setValue(new TestBean(text)); - } - }); - - Map inputMap = new HashMap(); - inputMap.put(new Integer(1), "rod"); - inputMap.put(new Integer(2), "rob"); - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("map", new ReadOnlyMap(inputMap)); - bw.setPropertyValues(pvs); - assertEquals("rod", ((TestBean) bean.getMap().get(new Integer(1))).getName()); - assertEquals("rob", ((TestBean) bean.getMap().get(new Integer(2))).getName()); - } - - @SuppressWarnings("unchecked") // must work with raw map in this test - @Test - public void testRawMapAccessWithNoEditorRegistered() { - IndexedTestBean bean = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - Map inputMap = new HashMap(); - inputMap.put(new Integer(1), "rod"); - inputMap.put(new Integer(2), "rob"); - ReadOnlyMap readOnlyMap = new ReadOnlyMap(inputMap); - MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("map", readOnlyMap); - bw.setPropertyValues(pvs); - assertSame(readOnlyMap, bean.getMap()); - assertFalse(readOnlyMap.isAccessed()); - } - - @Test - public void testTypedMapReadOnlyMap() { - TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean())); - TypedReadOnlyMapClient bean = new TypedReadOnlyMapClient(); - BeanWrapper bw = new BeanWrapperImpl(bean); - bw.setPropertyValue("map", map); - } - - @Test - public void testPrimitiveArray() { - PrimitiveArrayBean tb = new PrimitiveArrayBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.setPropertyValue("array", new String[] {"1", "2"}); - assertEquals(2, tb.getArray().length); - assertEquals(1, tb.getArray()[0]); - assertEquals(2, tb.getArray()[1]); - } - - @Test - public void testLargeMatchingPrimitiveArray() { - Assume.group(TestGroup.PERFORMANCE); - Assume.notLogging(LogFactory.getLog(BeanWrapperTests.class)); - - PrimitiveArrayBean tb = new PrimitiveArrayBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - int[] input = new int[1024]; - StopWatch sw = new StopWatch(); - sw.start("array1"); - for (int i = 0; i < 1000; i++) { - bw.setPropertyValue("array", input); - } - sw.stop(); - assertEquals(1024, tb.getArray().length); - assertEquals(0, tb.getArray()[0]); - long time1 = sw.getLastTaskTimeMillis(); - assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100); - - bw.registerCustomEditor(String.class, new StringTrimmerEditor(false)); - sw.start("array2"); - for (int i = 0; i < 1000; i++) { - bw.setPropertyValue("array", input); - } - sw.stop(); - assertTrue("Took too long", sw.getLastTaskTimeMillis() < 125); - - bw.registerCustomEditor(int.class, "array.somePath", new CustomNumberEditor(Integer.class, false)); - sw.start("array3"); - for (int i = 0; i < 1000; i++) { - bw.setPropertyValue("array", input); - } - sw.stop(); - assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100); - - bw.registerCustomEditor(int.class, "array[0].somePath", new CustomNumberEditor(Integer.class, false)); - sw.start("array3"); - for (int i = 0; i < 1000; i++) { - bw.setPropertyValue("array", input); - } - sw.stop(); - assertTrue("Took too long", sw.getLastTaskTimeMillis() < 100); - - bw.registerCustomEditor(int.class, new CustomNumberEditor(Integer.class, false)); - sw.start("array4"); - for (int i = 0; i < 100; i++) { - bw.setPropertyValue("array", input); - } - sw.stop(); - assertEquals(1024, tb.getArray().length); - assertEquals(0, tb.getArray()[0]); - assertTrue("Took too long", sw.getLastTaskTimeMillis() > time1); - } - - @Test - public void testLargeMatchingPrimitiveArrayWithSpecificEditor() { - PrimitiveArrayBean tb = new PrimitiveArrayBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.registerCustomEditor(int.class, "array", new PropertyEditorSupport() { - @Override - public void setValue(Object value) { - if (value instanceof Integer) { - super.setValue(new Integer(((Integer) value).intValue() + 1)); - } - } - }); - int[] input = new int[1024]; - bw.setPropertyValue("array", input); - assertEquals(1024, tb.getArray().length); - assertEquals(1, tb.getArray()[0]); - assertEquals(1, tb.getArray()[1]); - } - - @Test - public void testLargeMatchingPrimitiveArrayWithIndexSpecificEditor() { - PrimitiveArrayBean tb = new PrimitiveArrayBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.registerCustomEditor(int.class, "array[1]", new PropertyEditorSupport() { - @Override - public void setValue(Object value) { - if (value instanceof Integer) { - super.setValue(new Integer(((Integer) value).intValue() + 1)); - } - } - }); - int[] input = new int[1024]; - bw.setPropertyValue("array", input); - assertEquals(1024, tb.getArray().length); - assertEquals(0, tb.getArray()[0]); - assertEquals(1, tb.getArray()[1]); - } - - @Test - public void testPropertiesInProtectedBaseBean() { - DerivedFromProtectedBaseBean bean = new DerivedFromProtectedBaseBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - bw.setPropertyValue("someProperty", "someValue"); - assertEquals("someValue", bw.getPropertyValue("someProperty")); - assertEquals("someValue", bean.getSomeProperty()); - } - - @Test - public void testErrorMessageOfNestedProperty() { - ITestBean parent = new TestBean(); - ITestBean child = new DifferentTestBean(); - child.setName("test"); - parent.setSpouse(child); - BeanWrapper bw = new BeanWrapperImpl(parent); - try { - bw.getPropertyValue("spouse.bla"); - } - catch (NotReadablePropertyException ex) { - assertTrue(ex.getMessage().indexOf(TestBean.class.getName()) != -1); - } - } - - @Test - public void testMatchingCollections() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Collection coll = new HashSet(); - coll.add("coll1"); - bw.setPropertyValue("collection", coll); - Set set = new HashSet(); - set.add("set1"); - bw.setPropertyValue("set", set); - SortedSet sortedSet = new TreeSet(); - sortedSet.add("sortedSet1"); - bw.setPropertyValue("sortedSet", sortedSet); - List list = new LinkedList(); - list.add("list1"); - bw.setPropertyValue("list", list); - assertSame(coll, tb.getCollection()); - assertSame(set, tb.getSet()); - assertSame(sortedSet, tb.getSortedSet()); - assertSame(list, tb.getList()); - } - - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests - @Test - public void testNonMatchingCollections() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Collection coll = new ArrayList(); - coll.add("coll1"); - bw.setPropertyValue("collection", coll); - List set = new LinkedList(); - set.add("set1"); - bw.setPropertyValue("set", set); - List sortedSet = new ArrayList(); - sortedSet.add("sortedSet1"); - bw.setPropertyValue("sortedSet", sortedSet); - Set list = new HashSet(); - list.add("list1"); - bw.setPropertyValue("list", list); - assertEquals(1, tb.getCollection().size()); - assertTrue(tb.getCollection().containsAll(coll)); - assertEquals(1, tb.getSet().size()); - assertTrue(tb.getSet().containsAll(set)); - assertEquals(1, tb.getSortedSet().size()); - assertTrue(tb.getSortedSet().containsAll(sortedSet)); - assertEquals(1, tb.getList().size()); - assertTrue(tb.getList().containsAll(list)); - } - - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests - @Test - public void testCollectionsWithArrayValues() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Collection coll = new HashSet(); - coll.add("coll1"); - bw.setPropertyValue("collection", coll.toArray()); - List set = new LinkedList(); - set.add("set1"); - bw.setPropertyValue("set", set.toArray()); - List sortedSet = new ArrayList(); - sortedSet.add("sortedSet1"); - bw.setPropertyValue("sortedSet", sortedSet.toArray()); - Set list = new HashSet(); - list.add("list1"); - bw.setPropertyValue("list", list.toArray()); - assertEquals(1, tb.getCollection().size()); - assertTrue(tb.getCollection().containsAll(coll)); - assertEquals(1, tb.getSet().size()); - assertTrue(tb.getSet().containsAll(set)); - assertEquals(1, tb.getSortedSet().size()); - assertTrue(tb.getSortedSet().containsAll(sortedSet)); - assertEquals(1, tb.getList().size()); - assertTrue(tb.getList().containsAll(list)); - } - - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests - @Test - public void testCollectionsWithIntArrayValues() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Collection coll = new HashSet(); - coll.add(new Integer(0)); - bw.setPropertyValue("collection", new int[] {0}); - List set = new LinkedList(); - set.add(new Integer(1)); - bw.setPropertyValue("set", new int[] {1}); - List sortedSet = new ArrayList(); - sortedSet.add(new Integer(2)); - bw.setPropertyValue("sortedSet", new int[] {2}); - Set list = new HashSet(); - list.add(new Integer(3)); - bw.setPropertyValue("list", new int[] {3}); - assertEquals(1, tb.getCollection().size()); - assertTrue(tb.getCollection().containsAll(coll)); - assertEquals(1, tb.getSet().size()); - assertTrue(tb.getSet().containsAll(set)); - assertEquals(1, tb.getSortedSet().size()); - assertTrue(tb.getSortedSet().containsAll(sortedSet)); - assertEquals(1, tb.getList().size()); - assertTrue(tb.getList().containsAll(list)); - } - - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests - @Test - public void testCollectionsWithIntegerValues() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Collection coll = new HashSet(); - coll.add(new Integer(0)); - bw.setPropertyValue("collection", new Integer(0)); - List set = new LinkedList(); - set.add(new Integer(1)); - bw.setPropertyValue("set", new Integer(1)); - List sortedSet = new ArrayList(); - sortedSet.add(new Integer(2)); - bw.setPropertyValue("sortedSet", new Integer(2)); - Set list = new HashSet(); - list.add(new Integer(3)); - bw.setPropertyValue("list", new Integer(3)); - assertEquals(1, tb.getCollection().size()); - assertTrue(tb.getCollection().containsAll(coll)); - assertEquals(1, tb.getSet().size()); - assertTrue(tb.getSet().containsAll(set)); - assertEquals(1, tb.getSortedSet().size()); - assertTrue(tb.getSortedSet().containsAll(sortedSet)); - assertEquals(1, tb.getList().size()); - assertTrue(tb.getList().containsAll(list)); - } - - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests - @Test - public void testCollectionsWithStringValues() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - List set = new LinkedList(); - set.add("set1"); - bw.setPropertyValue("set", "set1"); - List sortedSet = new ArrayList(); - sortedSet.add("sortedSet1"); - bw.setPropertyValue("sortedSet", "sortedSet1"); - Set list = new HashSet(); - list.add("list1"); - bw.setPropertyValue("list", "list1"); - assertEquals(1, tb.getSet().size()); - assertTrue(tb.getSet().containsAll(set)); - assertEquals(1, tb.getSortedSet().size()); - assertTrue(tb.getSortedSet().containsAll(sortedSet)); - assertEquals(1, tb.getList().size()); - assertTrue(tb.getList().containsAll(list)); - } - - @Test - public void testCollectionsWithStringValuesAndCustomEditor() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - bw.registerCustomEditor(String.class, "set", new StringTrimmerEditor(false)); - bw.registerCustomEditor(String.class, "list", new StringTrimmerEditor(false)); - - bw.setPropertyValue("set", "set1 "); - bw.setPropertyValue("sortedSet", "sortedSet1"); - bw.setPropertyValue("list", "list1 "); - assertEquals(1, tb.getSet().size()); - assertTrue(tb.getSet().contains("set1")); - assertEquals(1, tb.getSortedSet().size()); - assertTrue(tb.getSortedSet().contains("sortedSet1")); - assertEquals(1, tb.getList().size()); - assertTrue(tb.getList().contains("list1")); - - bw.setPropertyValue("list", Arrays.asList(new String[] {"list1 "})); - assertTrue(tb.getList().contains("list1")); - } - - @Test - public void testMatchingMaps() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Map map = new HashMap(); - map.put("key", "value"); - bw.setPropertyValue("map", map); - SortedMap sortedMap = new TreeMap(); - map.put("sortedKey", "sortedValue"); - bw.setPropertyValue("sortedMap", sortedMap); - assertSame(map, tb.getMap()); - assertSame(sortedMap, tb.getSortedMap()); - } - - @Test - public void testNonMatchingMaps() { - IndexedTestBean tb = new IndexedTestBean(); - BeanWrapper bw = new BeanWrapperImpl(tb); - Map map = new TreeMap(); - map.put("key", "value"); - bw.setPropertyValue("map", map); - Map sortedMap = new TreeMap(); - sortedMap.put("sortedKey", "sortedValue"); - bw.setPropertyValue("sortedMap", sortedMap); - assertEquals(1, tb.getMap().size()); - assertEquals("value", tb.getMap().get("key")); - assertEquals(1, tb.getSortedMap().size()); - assertEquals("sortedValue", tb.getSortedMap().get("sortedKey")); - } - - @Test - public void testSetNumberProperties() { - NumberPropertyBean bean = new NumberPropertyBean(); - BeanWrapper bw = new BeanWrapperImpl(bean); - - String byteValue = " " + Byte.MAX_VALUE + " "; - String shortValue = " " + Short.MAX_VALUE + " "; - String intValue = " " + Integer.MAX_VALUE + " "; - String longValue = " " + Long.MAX_VALUE + " "; - String floatValue = " " + Float.MAX_VALUE + " "; - String doubleValue = " " + Double.MAX_VALUE + " "; - - bw.setPropertyValue("myPrimitiveByte", byteValue); - bw.setPropertyValue("myByte", byteValue); - - bw.setPropertyValue("myPrimitiveShort", shortValue); - bw.setPropertyValue("myShort", shortValue); - - bw.setPropertyValue("myPrimitiveInt", intValue); - bw.setPropertyValue("myInteger", intValue); - - bw.setPropertyValue("myPrimitiveLong", longValue); - bw.setPropertyValue("myLong", longValue); - - bw.setPropertyValue("myPrimitiveFloat", floatValue); - bw.setPropertyValue("myFloat", floatValue); - - bw.setPropertyValue("myPrimitiveDouble", doubleValue); - bw.setPropertyValue("myDouble", doubleValue); - - assertEquals(Byte.MAX_VALUE, bean.getMyPrimitiveByte()); - assertEquals(Byte.MAX_VALUE, bean.getMyByte().byteValue()); - - assertEquals(Short.MAX_VALUE, bean.getMyPrimitiveShort()); - assertEquals(Short.MAX_VALUE, bean.getMyShort().shortValue()); + * @author Chris Beams + * @author Dave Syer + */ +public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessorTests { - assertEquals(Integer.MAX_VALUE, bean.getMyPrimitiveInt()); - assertEquals(Integer.MAX_VALUE, bean.getMyInteger().intValue()); + @Override + protected BeanWrapperImpl createAccessor(Object target) { + return new BeanWrapperImpl(target); + } - assertEquals(Long.MAX_VALUE, bean.getMyPrimitiveLong()); - assertEquals(Long.MAX_VALUE, bean.getMyLong().longValue()); + @Test + public void setterDoestNotCallGetter() { + GetterBean target = new GetterBean(); + BeanWrapper accessor = createAccessor(target); + accessor.setPropertyValue("name", "tom"); + assertTrue("Set name to tom", target.getName().equals("tom")); + } - assertEquals(Float.MAX_VALUE, bean.getMyPrimitiveFloat(), 0.001); - assertEquals(Float.MAX_VALUE, bean.getMyFloat().floatValue(), 0.001); + @Test + public void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() { + TestBean target = new TestBean(); + String newName = "tony"; + String invalidTouchy = ".valid"; + try { + BeanWrapper accessor = createAccessor(target); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.addPropertyValue(new PropertyValue("age", "foobar")); + pvs.addPropertyValue(new PropertyValue("name", newName)); + pvs.addPropertyValue(new PropertyValue("touchy", invalidTouchy)); + accessor.setPropertyValues(pvs); + fail("Should throw exception when everything is valid"); + } + catch (PropertyBatchUpdateException ex) { + assertTrue("Must contain 2 exceptions", ex.getExceptionCount() == 2); + // Test validly set property matches + assertTrue("Vaid set property must stick", target.getName().equals(newName)); + assertTrue("Invalid set property must retain old value", target.getAge() == 0); + assertTrue("New value of dodgy setter must be available through exception", + ex.getPropertyAccessException("touchy").getPropertyChangeEvent().getNewValue().equals(invalidTouchy)); + } + } - assertEquals(Double.MAX_VALUE, bean.getMyPrimitiveDouble(), 0.001); - assertEquals(Double.MAX_VALUE, bean.getMyDouble().doubleValue(), 0.001); + @Test + public void checkNotWritablePropertyHoldPossibleMatches() { + TestBean target = new TestBean(); + try { + BeanWrapper accessor = createAccessor(target); + accessor.setPropertyValue("ag", "foobar"); + fail("Should throw exception on invalid property"); + } + catch (NotWritablePropertyException ex) { + // expected + assertEquals(1, ex.getPossibleMatches().length); + assertEquals("age", ex.getPossibleMatches()[0]); + } + } + @Test // Can't be shared; there is no such thing as a read-only field + public void setReadOnlyMapProperty() { + TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean())); + TypedReadOnlyMapClient target = new TypedReadOnlyMapClient(); + BeanWrapper accessor = createAccessor(target); + accessor.setPropertyValue("map", map); } @Test - public void testAlternativesForTypo() { - IntelliBean ib = new IntelliBean(); - BeanWrapper bw = new BeanWrapperImpl(ib); + public void notWritablePropertyExceptionContainsAlternativeMatch() { + IntelliBean target = new IntelliBean(); + BeanWrapper bw = createAccessor(target); try { bw.setPropertyValue("names", "Alef"); } @@ -1512,9 +112,9 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor } @Test - public void testAlternativesForTypos() { - IntelliBean ib = new IntelliBean(); - BeanWrapper bw = new BeanWrapperImpl(ib); + public void notWritablePropertyExceptionContainsAlternativeMatches() { + IntelliBean target = new IntelliBean(); + BeanWrapper bw = createAccessor(target); try { bw.setPropertyValue("mystring", "Arjen"); } @@ -1524,191 +124,49 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor } } - @Test - public void testGenericEnum() { - EnumConsumer consumer = new EnumConsumer(); - BeanWrapper bw = new BeanWrapperImpl(consumer); - bw.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE"); - assertEquals(TestEnum.TEST_VALUE, consumer.getEnumValue()); - } - - @Test - public void testWildcardedGenericEnum() { - WildcardEnumConsumer consumer = new WildcardEnumConsumer(); - BeanWrapper bw = new BeanWrapperImpl(consumer); - bw.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE"); - assertEquals(TestEnum.TEST_VALUE, consumer.getEnumValue()); - } - - @Test - public void cornerSpr10115() { - Spr10115Bean foo = new Spr10115Bean(); - BeanWrapperImpl bwi = new BeanWrapperImpl(foo); - bwi.setPropertyValue("prop1", "val1"); - assertEquals("val1", Spr10115Bean.prop1); - } - - @Test - public void testArrayToObject() { - ArrayToObject foo = new ArrayToObject(); - BeanWrapperImpl bwi = new BeanWrapperImpl(foo); - - Object[] array = new Object[] {"1","2"}; - bwi.setPropertyValue("object", array); - assertThat(foo.getObject(), equalTo((Object) array)); - - array = new Object[] {"1"}; - bwi.setPropertyValue("object", array); - assertThat(foo.getObject(), equalTo((Object) array)); - } - - @Test - public void testPropertyTypeMismatch() { - PropertyTypeMismatch foo = new PropertyTypeMismatch(); - BeanWrapperImpl bwi = new BeanWrapperImpl(foo); - bwi.setPropertyValue("object", "a String"); - assertEquals("a String", foo.value); - assertTrue(foo.getObject() == 8); - assertEquals(8, bwi.getPropertyValue("object")); + @Test // Can't be shared: no type mismatch with a field") + public void setPropertyTypeMismatch() { + PropertyTypeMismatch target = new PropertyTypeMismatch(); + BeanWrapper accessor = createAccessor(target); + accessor.setPropertyValue("object", "a String"); + assertEquals("a String", target.value); + assertTrue(target.getObject() == 8); + assertEquals(8, accessor.getPropertyValue("object")); } @Test - public void testGetterWithOptional() { - GetterWithOptional foo = new GetterWithOptional(); + public void getPropertyWithOptional() { + GetterWithOptional target = new GetterWithOptional(); TestBean tb = new TestBean("x"); - BeanWrapperImpl bwi = new BeanWrapperImpl(foo); + BeanWrapper accessor = createAccessor(target); - bwi.setPropertyValue("object", tb); - assertSame(tb, foo.value); - assertSame(tb, foo.getObject().get()); - assertSame(tb, ((Optional) bwi.getPropertyValue("object")).get()); - assertEquals("x", foo.value.getName()); - assertEquals("x", foo.getObject().get().getName()); - assertEquals("x", bwi.getPropertyValue("object.name")); - - bwi.setPropertyValue("object.name", "y"); - assertSame(tb, foo.value); - assertSame(tb, foo.getObject().get()); - assertSame(tb, ((Optional) bwi.getPropertyValue("object")).get()); - assertEquals("y", foo.value.getName()); - assertEquals("y", foo.getObject().get().getName()); - assertEquals("y", bwi.getPropertyValue("object.name")); - } - - @Test - public void testGetterWithOptionalAndAutoGrowing() { - GetterWithOptional foo = new GetterWithOptional(); - BeanWrapperImpl bwi = new BeanWrapperImpl(foo); - bwi.setAutoGrowNestedPaths(true); + accessor.setPropertyValue("object", tb); + assertSame(tb, target.value); + assertSame(tb, target.getObject().get()); + assertSame(tb, ((Optional) accessor.getPropertyValue("object")).get()); + assertEquals("x", target.value.getName()); + assertEquals("x", target.getObject().get().getName()); + assertEquals("x", accessor.getPropertyValue("object.name")); - bwi.setPropertyValue("object.name", "x"); - assertEquals("x", foo.value.getName()); - assertEquals("x", foo.getObject().get().getName()); - assertEquals("x", bwi.getPropertyValue("object.name")); + accessor.setPropertyValue("object.name", "y"); + assertSame(tb, target.value); + assertSame(tb, target.getObject().get()); + assertSame(tb, ((Optional) accessor.getPropertyValue("object")).get()); + assertEquals("y", target.value.getName()); + assertEquals("y", target.getObject().get().getName()); + assertEquals("y", accessor.getPropertyValue("object.name")); } @Test - public void testGenericArraySetter() { - SkipReaderStub foo = new SkipReaderStub(); - BeanWrapperImpl bwi = new BeanWrapperImpl(foo); - List values = new LinkedList(); - values.add("1"); - values.add("2"); - values.add("3"); - values.add("4"); - bwi.setPropertyValue("items", values); - Object[] result = foo.items; - assertEquals(4, result.length); - assertEquals("1", result[0]); - assertEquals("2", result[1]); - assertEquals("3", result[2]); - assertEquals("4", result[3]); - } - - - static class Spr10115Bean { - - private static String prop1; - - public static void setProp1(String prop1) { - Spr10115Bean.prop1 = prop1; - } - } - + public void getPropertyWithOptionalAndAutoGrow() { + GetterWithOptional target = new GetterWithOptional(); + BeanWrapper accessor = createAccessor(target); + accessor.setAutoGrowNestedPaths(true); - @SuppressWarnings("unused") - private static class Foo { - - private List list; - - private List listOfMaps; - - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - public List getListOfMaps() { - return listOfMaps; - } - - public void setListOfMaps(List listOfMaps) { - this.listOfMaps = listOfMaps; - } - } - - - private static class DifferentTestBean extends TestBean { - // class to test naming of beans in a BeanWrapper error message - } - - - - - @SuppressWarnings("unused") - private static class EnumTester { - - private Autowire autowire; - - public void setAutowire(Autowire autowire) { - this.autowire = autowire; - } - - public Autowire getAutowire() { - return autowire; - } - } - - - @SuppressWarnings("unused") - private static class PropsTester { - - private Properties props; - - private String name; - - private String[] stringArray; - - private int[] intArray; - - public void setProperties(Properties p) { - props = p; - } - - public void setName(String name) { - this.name = name; - } - - public void setStringArray(String[] sa) { - this.stringArray = sa; - } - - public void setIntArray(int[] intArray) { - this.intArray = intArray; - } + accessor.setPropertyValue("object.name", "x"); + assertEquals("x", target.value.getName()); + assertEquals("x", target.getObject().get().getName()); + assertEquals("x", accessor.getPropertyValue("object.name")); } @@ -1729,240 +187,22 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor } } - - @SuppressWarnings("unused") - private static class ThrowsException { - - public void doSomething(Throwable t) throws Throwable { - throw t; - } - } - - - @SuppressWarnings("unused") - private static class PrimitiveArrayBean { - - private int[] array; - - public int[] getArray() { - return array; - } - - public void setArray(int[] array) { - this.array = array; - } - } - - @SuppressWarnings("unused") - private static class StringArrayBean { - - private String[] array; - - public String[] getArray() { - return array; - } - - public void setArray(String[] array) { - this.array = array; - } - } - - - @SuppressWarnings("unused") - private static class NumberPropertyBean { - - private byte myPrimitiveByte; - private Byte myByte; - - private short myPrimitiveShort; - private Short myShort; - - private int myPrimitiveInt; - private Integer myInteger; - - private long myPrimitiveLong; - private Long myLong; - - private float myPrimitiveFloat; - private Float myFloat; - - private double myPrimitiveDouble; - private Double myDouble; - - public byte getMyPrimitiveByte() { - return myPrimitiveByte; - } - - public void setMyPrimitiveByte(byte myPrimitiveByte) { - this.myPrimitiveByte = myPrimitiveByte; - } - - public Byte getMyByte() { - return myByte; - } - - public void setMyByte(Byte myByte) { - this.myByte = myByte; - } - - public short getMyPrimitiveShort() { - return myPrimitiveShort; - } - - public void setMyPrimitiveShort(short myPrimitiveShort) { - this.myPrimitiveShort = myPrimitiveShort; - } - - public Short getMyShort() { - return myShort; - } - - public void setMyShort(Short myShort) { - this.myShort = myShort; - } - - public int getMyPrimitiveInt() { - return myPrimitiveInt; - } - - public void setMyPrimitiveInt(int myPrimitiveInt) { - this.myPrimitiveInt = myPrimitiveInt; - } - - public Integer getMyInteger() { - return myInteger; - } - - public void setMyInteger(Integer myInteger) { - this.myInteger = myInteger; - } - - public long getMyPrimitiveLong() { - return myPrimitiveLong; - } - - public void setMyPrimitiveLong(long myPrimitiveLong) { - this.myPrimitiveLong = myPrimitiveLong; - } - - public Long getMyLong() { - return myLong; - } - - public void setMyLong(Long myLong) { - this.myLong = myLong; - } - - public float getMyPrimitiveFloat() { - return myPrimitiveFloat; - } - - public void setMyPrimitiveFloat(float myPrimitiveFloat) { - this.myPrimitiveFloat = myPrimitiveFloat; - } - - public Float getMyFloat() { - return myFloat; - } - - public void setMyFloat(Float myFloat) { - this.myFloat = myFloat; - } - - public double getMyPrimitiveDouble() { - return myPrimitiveDouble; - } - - public void setMyPrimitiveDouble(double myPrimitiveDouble) { - this.myPrimitiveDouble = myPrimitiveDouble; - } - - public Double getMyDouble() { - return myDouble; - } - - public void setMyDouble(Double myDouble) { - this.myDouble = myDouble; - } - } - - @SuppressWarnings("unused") private static class IntelliBean { - public void setName(String name) {} - - public void setMyString(String string) {} - - public void setMyStrings(String string) {} - - public void setMyStriNg(String string) {} - - public void setMyStringss(String string) {} - } - - - @SuppressWarnings("unused") - private static class Employee extends TestBean { - - private String co; - - public String getCompany() { - return co; - } - - public void setCompany(String co) { - this.co = co; - } - } - - - @SuppressWarnings("serial") - public static class ReadOnlyMap extends HashMap { - - private boolean frozen = false; - - private boolean accessed = false; - - public ReadOnlyMap() { - this.frozen = true; - } - - public ReadOnlyMap(Map map) { - super(map); - this.frozen = true; - } - - @Override - public V put(K key, V value) { - if (this.frozen) { - throw new UnsupportedOperationException(); - } - else { - return super.put(key, value); - } + public void setName(String name) { } - @Override - public Set> entrySet() { - this.accessed = true; - return super.entrySet(); + public void setMyString(String string) { } - @Override - public Set keySet() { - this.accessed = true; - return super.keySet(); + public void setMyStrings(String string) { } - @Override - public int size() { - this.accessed = true; - return super.size(); + public void setMyStriNg(String string) { } - public boolean isAccessed() { - return this.accessed; + public void setMyStringss(String string) { } } @@ -1986,54 +226,6 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor } - public static class EnumConsumer { - - private Enum enumValue; - - public Enum getEnumValue() { - return enumValue; - } - - public void setEnumValue(Enum enumValue) { - this.enumValue = enumValue; - } - } - - - public static class WildcardEnumConsumer { - - private Enum enumValue; - - public Enum getEnumValue() { - return enumValue; - } - - public void setEnumValue(Enum enumValue) { - this.enumValue = enumValue; - } - } - - - public enum TestEnum { - - TEST_VALUE - } - - - public static class ArrayToObject { - - private Object object; - - public void setObject(Object object) { - this.object = object; - } - - public Object getObject() { - return object; - } - } - - public static class PropertyTypeMismatch { public String value; @@ -2061,21 +253,4 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor } } - - public static class SkipReaderStub { - - public T[] items; - - public SkipReaderStub() { - } - - public SkipReaderStub(T... items) { - this.items = items; - } - - public void setItems(T... items) { - this.items = items; - } - } - } 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 75227020ba..543f12f406 100644 --- a/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * 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. @@ -23,27 +23,29 @@ import org.springframework.tests.sample.beans.TestBean; import static org.junit.Assert.*; /** - * Unit tests for {@link DirectFieldAccessor} + * Specific {@link DirectFieldAccessor} tests. * * @author Jose Luis Martin * @author Chris Beams + * @@author Stephane Nicoll */ public class DirectFieldAccessorTests extends AbstractConfigurablePropertyAccessorTests { @Override - protected ConfigurablePropertyAccessor createAccessor(Object target) { + protected DirectFieldAccessor createAccessor(Object target) { return new DirectFieldAccessor(target); } + @Test public void withShadowedField() throws Exception { @SuppressWarnings("serial") - TestBean tb = new TestBean() { + TestBean target = new TestBean() { @SuppressWarnings("unused") StringBuilder name = new StringBuilder(); }; - DirectFieldAccessor dfa = new DirectFieldAccessor(tb); + DirectFieldAccessor dfa = createAccessor(target); assertEquals(StringBuilder.class, dfa.getPropertyType("name")); } diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java index eb18ea6a9f..da243abbc9 100644 --- a/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * 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. @@ -38,6 +38,7 @@ import org.springframework.util.ObjectUtils; * * @author Rod Johnson * @author Juergen Hoeller + * @author Stephane Nicoll * @since 15 April 2001 */ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOther, Comparable { @@ -58,6 +59,8 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt private boolean jedi; + private ITestBean spouse; + protected ITestBean[] spouses; private String touchy; @@ -113,7 +116,7 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt } public TestBean(ITestBean spouse) { - this.spouses = new ITestBean[] {spouse}; + this.spouse = spouse; } public TestBean(String name, int age) { @@ -122,7 +125,7 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt } public TestBean(ITestBean spouse, Properties someProperties) { - this.spouses = new ITestBean[] {spouse}; + this.spouse = spouse; this.someProperties = someProperties; } @@ -210,17 +213,17 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt @Override public ITestBean getSpouse() { - return (spouses != null ? spouses[0] : null); + return this.spouse; } @Override public void setSpouse(ITestBean spouse) { - this.spouses = new ITestBean[] {spouse}; + this.spouse = spouse; } @Override public ITestBean[] getSpouses() { - return spouses; + return (spouse != null ? new ITestBean[]{spouse} : null); } public String getTouchy() { diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java index 6ec3f5b580..32b4c0e114 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java @@ -345,11 +345,11 @@ public class WebRequestDataBinderTests { static class TestBeanWithConcreteSpouse extends TestBean { public void setConcreteSpouse(TestBean spouse) { - this.spouses = new ITestBean[] {spouse}; + setSpouse(spouse); } public TestBean getConcreteSpouse() { - return (spouses != null ? (TestBean) spouses[0] : null); + return (TestBean) getSpouse(); } }