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 extends K, ? extends V> 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 extends K, ? extends V> 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();
}
}