提交 3d86f15a 编写于 作者: S Stephane Nicoll

Merge BeanWrapperImpl and DirectFieldAccessor

`BeanWrapperImpl` and `DirectFieldAccessor` are two
`ConfigurablePropertyAccessor` implementations with different features
set.

This commit harmonizes the two implementations to use a common base class
that delegates the actual property handling to the sub-classes:

* `BeanWrapperImpl`:  `PropertyDescriptor` and introspection utilities
* `DirectFieldAccessor`: reflection on `java.lang.Field`

Issues: SPR-12206 - SPR-12805
上级 ad4c8795
/*
* 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();
......
/*
* 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.
*
* <p>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<String, AbstractPropertyAccessor> 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.
* <p>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<Object> list = (List<Object>) 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<Object, Object> map = (Map<Object, Object>) 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<Object> list = (List<Object>) 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<Object> set = (Set<Object>) 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<Object> 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<Object, Object> map = (Map<Object, Object>) 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<Object> 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.
* <p>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<String, AbstractPropertyAccessor>();
}
// 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<String> keys = new ArrayList<String>(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();
}
}
}
......@@ -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.
*
* <p>{@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.
*
* <p><b>NOTE: As of Spring 2.5, this is - for almost all purposes - an
* internal class.</b> 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<String, BeanWrapperImpl> 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.
* <p>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.
* <p>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.
* <p>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<String, BeanWrapperImpl>();
}
// 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.
* <p>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<String> keys = new ArrayList<String>(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<Object>() {
......@@ -748,13 +288,12 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
}
}
Object value;
if (System.getSecurityManager() != null) {
try {
value = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@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<Object> list = (List<Object>) 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<Object> set = (Set<Object>) 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<Object> 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<Object, Object> map = (Map<Object, Object>) 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<Object> 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<Object>() {
@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<Object> list = (List<Object>) 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<Object>() {
@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<Object, Object> map = (Map<Object, Object>) 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<Object>() {
@Override
public Object run() {
readMethod.setAccessible(true);
return null;
}
});
}
else {
readMethod.setAccessible(true);
}
}
try {
if (System.getSecurityManager() != null) {
oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@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<Object>() {
@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<Object>() {
@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();
}
}
......
/*
* 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.
*
* <p>As of Spring 4.1, this implementation supports nested field traversal.
* <p>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.
*
* <p>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<String, FieldPropertyHandler> fieldMap = new HashMap<String, FieldPropertyHandler>();
private final Map<String, FieldAccessor> fieldMap = new HashMap<String, FieldAccessor>();
/**
* 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<FieldAccessor> 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<FieldAccessor> buildParents(FieldAccessor parent) {
List<FieldAccessor> parents = new ArrayList<FieldAccessor>();
if (parent != null) {
parents.addAll(parent.parents);
parents.add(parent);
throw new InvalidPropertyException(getWrappedClass(), this.field.getName(),
"Field is not accessible", ex);
}
return parents;
}
}
......
/*
* 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);
}
......
/*
* 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<String, String> map = new HashMap<String, String>();
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<String> list = new ArrayList<String>();
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<String> set = new HashSet<String>();
set.add("foo");
set.add("fi");
set.add("fum");
accessor.setPropertyValue("stringArray", set);
assertTrue("stringArray length = 3", target.stringArray.length == 3);
List<String> 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<String> list = new ArrayList<String>();
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<String> set = new HashSet<String>();
set.add("4foo");
set.add("7fi");
set.add("6fum");
accessor.setPropertyValue("stringArray", set);
assertTrue("stringArray length = 3", target.stringArray.length == 3);
List<String> 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<Object> 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<Object> 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<Integer> 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<String> values = new LinkedList<String>();
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<String> coll = new HashSet<String>();
coll.add("coll1");
accessor.setPropertyValue("collection", coll);
Set<String> set = new HashSet<String>();
set.add("set1");
accessor.setPropertyValue("set", set);
SortedSet<String> sortedSet = new TreeSet<String>();
sortedSet.add("sortedSet1");
accessor.setPropertyValue("sortedSet", sortedSet);
List<String> list = new LinkedList<String>();
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<String> coll = new ArrayList<String>();
coll.add("coll1");
accessor.setPropertyValue("collection", coll);
List<String> set = new LinkedList<String>();
set.add("set1");
accessor.setPropertyValue("set", set);
List<String> sortedSet = new ArrayList<String>();
sortedSet.add("sortedSet1");
accessor.setPropertyValue("sortedSet", sortedSet);
Set<String> list = new HashSet<String>();
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<String> coll = new HashSet<String>();
coll.add("coll1");
accessor.setPropertyValue("collection", coll.toArray());
List<String> set = new LinkedList<String>();
set.add("set1");
accessor.setPropertyValue("set", set.toArray());
List<String> sortedSet = new ArrayList<String>();
sortedSet.add("sortedSet1");
accessor.setPropertyValue("sortedSet", sortedSet.toArray());
Set<String> list = new HashSet<String>();
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<Integer> coll = new HashSet<Integer>();
coll.add(0);
accessor.setPropertyValue("collection", new int[] {0});
List<Integer> set = new LinkedList<Integer>();
set.add(1);
accessor.setPropertyValue("set", new int[] {1});
List<Integer> sortedSet = new ArrayList<Integer>();
sortedSet.add(2);
accessor.setPropertyValue("sortedSet", new int[] {2});
Set<Integer> list = new HashSet<Integer>();
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<Integer> coll = new HashSet<Integer>();
coll.add(0);
accessor.setPropertyValue("collection", new Integer(0));
List<Integer> set = new LinkedList<Integer>();
set.add(1);
accessor.setPropertyValue("set", new Integer(1));
List<Integer> sortedSet = new ArrayList<Integer>();
sortedSet.add(2);
accessor.setPropertyValue("sortedSet", new Integer(2));
Set<Integer> list = new HashSet<Integer>();
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<String> set = new LinkedList<String>();
set.add("set1");
accessor.setPropertyValue("set", "set1");
List<String> sortedSet = new ArrayList<String>();
sortedSet.add("sortedSet1");
accessor.setPropertyValue("sortedSet", "sortedSet1");
Set<String> list = new HashSet<String>();
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<String, String> map = new HashMap<String, String>();
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<String, String> map = new TreeMap<String, String>();
map.put("key", "value");
accessor.setPropertyValue("map", map);
Map<String, String> sortedMap = new TreeMap<String, String>();
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<Integer, String> inputMap = new HashMap<Integer, String>();
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<Object, Object> inputMap = new HashMap<Object, Object>();
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<Map> listOfMaps;
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public List<Map> getListOfMaps() {
return listOfMaps;
}
public void setListOfMaps(List<Map> 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<TestEnum> enumValue;
public Enum<TestEnum> getEnumValue() {
return enumValue;
}
public void setEnumValue(Enum<TestEnum> 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<T> {
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<K, V> extends HashMap<K, V> {
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<Map.Entry<K, V>> entrySet() {
this.accessed = true;
return super.entrySet();
}
@Override
public Set<K> keySet() {
this.accessed = true;
return super.keySet();
}
@Override
public int size() {
this.accessed = true;
return super.size();
}
public boolean isAccessed() {
return this.accessed;
}
}
}
......@@ -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<String, String> map = new HashMap<String, String>();
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<String> list = new ArrayList<String>();
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<String> set = new HashSet<String>();
set.add("foo");
set.add("fi");
set.add("fum");
bw.setPropertyValue("stringArray", set);
assertTrue("stringArray length = 3", pt.stringArray.length == 3);
List<String> 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<String> list = new ArrayList<String>();
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<String> set = new HashSet<String>();
set.add("4foo");
set.add("7fi");
set.add("6fum");
bw.setPropertyValue("stringArray", set);
assertTrue("stringArray length = 3", pt.stringArray.length == 3);
List<String> 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<Object> list = new ArrayList<Object>();
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<Object> set = new HashSet<Object>();
set.add("4");
set.add(new Integer(5));
set.add("3");
bw.setPropertyValue("intArray", set);
assertTrue("intArray length = 3", pt.intArray.length == 3);
List<Integer> result = new ArrayList<Integer>();
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<Integer, String> inputMap = new HashMap<Integer, String>();
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<Object, Object> inputMap = new HashMap<Object, Object>();
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<String> coll = new HashSet<String>();
coll.add("coll1");
bw.setPropertyValue("collection", coll);
Set<String> set = new HashSet<String>();
set.add("set1");
bw.setPropertyValue("set", set);
SortedSet<String> sortedSet = new TreeSet<String>();
sortedSet.add("sortedSet1");
bw.setPropertyValue("sortedSet", sortedSet);
List<String> list = new LinkedList<String>();
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<String> coll = new ArrayList<String>();
coll.add("coll1");
bw.setPropertyValue("collection", coll);
List<String> set = new LinkedList<String>();
set.add("set1");
bw.setPropertyValue("set", set);
List<String> sortedSet = new ArrayList<String>();
sortedSet.add("sortedSet1");
bw.setPropertyValue("sortedSet", sortedSet);
Set<String> list = new HashSet<String>();
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<String> coll = new HashSet<String>();
coll.add("coll1");
bw.setPropertyValue("collection", coll.toArray());
List<String> set = new LinkedList<String>();
set.add("set1");
bw.setPropertyValue("set", set.toArray());
List<String> sortedSet = new ArrayList<String>();
sortedSet.add("sortedSet1");
bw.setPropertyValue("sortedSet", sortedSet.toArray());
Set<String> list = new HashSet<String>();
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<Integer> coll = new HashSet<Integer>();
coll.add(new Integer(0));
bw.setPropertyValue("collection", new int[] {0});
List<Integer> set = new LinkedList<Integer>();
set.add(new Integer(1));
bw.setPropertyValue("set", new int[] {1});
List<Integer> sortedSet = new ArrayList<Integer>();
sortedSet.add(new Integer(2));
bw.setPropertyValue("sortedSet", new int[] {2});
Set<Integer> list = new HashSet<Integer>();
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<Integer> coll = new HashSet<Integer>();
coll.add(new Integer(0));
bw.setPropertyValue("collection", new Integer(0));
List<Integer> set = new LinkedList<Integer>();
set.add(new Integer(1));
bw.setPropertyValue("set", new Integer(1));
List<Integer> sortedSet = new ArrayList<Integer>();
sortedSet.add(new Integer(2));
bw.setPropertyValue("sortedSet", new Integer(2));
Set<Integer> list = new HashSet<Integer>();
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<String> set = new LinkedList<String>();
set.add("set1");
bw.setPropertyValue("set", "set1");
List<String> sortedSet = new ArrayList<String>();
sortedSet.add("sortedSet1");
bw.setPropertyValue("sortedSet", "sortedSet1");
Set<String> list = new HashSet<String>();
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<String, String> map = new HashMap<String, String>();
map.put("key", "value");
bw.setPropertyValue("map", map);
SortedMap<?, ?> sortedMap = new TreeMap<Object, Object>();
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<String, String> map = new TreeMap<String, String>();
map.put("key", "value");
bw.setPropertyValue("map", map);
Map<String, String> sortedMap = new TreeMap<String, String>();
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<String>) 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<String>) 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<String>) 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<String>) 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<String> values = new LinkedList<String>();
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<Map> listOfMaps;
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public List<Map> getListOfMaps() {
return listOfMaps;
}
public void setListOfMaps(List<Map> 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<K, V> extends HashMap<K, V> {
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<Map.Entry<K, V>> entrySet() {
this.accessed = true;
return super.entrySet();
public void setMyString(String string) {
}
@Override
public Set<K> 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<TestEnum> enumValue;
public Enum<TestEnum> getEnumValue() {
return enumValue;
}
public void setEnumValue(Enum<TestEnum> 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<T> {
public T[] items;
public SkipReaderStub() {
}
public SkipReaderStub(T... items) {
this.items = items;
}
public void setItems(T... items) {
this.items = items;
}
}
}
/*
* 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"));
}
......
/*
* 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<Object> {
......@@ -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() {
......
......@@ -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();
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册