AbstractNestablePropertyAccessor's setPropertyValue refactored into several delegate methods

Issue: SPR-15053
This commit is contained in:
Juergen Hoeller 2016-12-26 19:47:26 +01:00
parent 47be2d8786
commit 64d6561cbb
1 changed files with 182 additions and 171 deletions

View File

@ -266,79 +266,57 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
} }
} }
@SuppressWarnings("unchecked")
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName;
if (tokens.keys != null) { if (tokens.keys != null) {
// Apply indexes and map keys: fetch value for all keys but the last one. processKeyedProperty(tokens, pv);
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 { else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, processLocalProperty(tokens, pv);
"Cannot access indexed value in property referenced " +
"in indexed property path '" + propertyName + "': returned null");
} }
} }
@SuppressWarnings("unchecked")
private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
Object propValue = getPropertyHoldingValue(tokens);
String lastKey = tokens.keys[tokens.keys.length - 1];
if (propValue.getClass().isArray()) { if (propValue.getClass().isArray()) {
PropertyHandler ph = getLocalPropertyHandler(actualName); PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
Class<?> requiredType = propValue.getClass().getComponentType(); Class<?> requiredType = propValue.getClass().getComponentType();
int arrayIndex = Integer.parseInt(key); int arrayIndex = Integer.parseInt(lastKey);
Object oldValue = null; Object oldValue = null;
try { try {
if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
oldValue = Array.get(propValue, arrayIndex); oldValue = Array.get(propValue, arrayIndex);
} }
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType, ph.nested(tokens.keys.length)); requiredType, ph.nested(tokens.keys.length));
int length = Array.getLength(propValue); int length = Array.getLength(propValue);
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) { if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
Class<?> componentType = propValue.getClass().getComponentType(); Class<?> componentType = propValue.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, arrayIndex + 1); Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length); System.arraycopy(propValue, 0, newArray, 0, length);
setPropertyValue(actualName, newArray); setPropertyValue(tokens.actualName, newArray);
propValue = getPropertyValue(actualName); propValue = getPropertyValue(tokens.actualName);
} }
Array.set(propValue, arrayIndex, convertedValue); Array.set(propValue, arrayIndex, convertedValue);
} }
catch (IndexOutOfBoundsException ex) { catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Invalid array index in property path '" + propertyName + "'", ex); "Invalid array index in property path '" + tokens.canonicalName + "'", ex);
} }
} }
else if (propValue instanceof List) { else if (propValue instanceof List) {
PropertyHandler ph = getPropertyHandler(actualName); PropertyHandler ph = getPropertyHandler(tokens.actualName);
Class<?> requiredType = ph.getCollectionType(tokens.keys.length); Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
List<Object> list = (List<Object>) propValue; List<Object> list = (List<Object>) propValue;
int index = Integer.parseInt(key); int index = Integer.parseInt(lastKey);
Object oldValue = null; Object oldValue = null;
if (isExtractOldValueForEditor() && index < list.size()) { if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index); oldValue = list.get(index);
} }
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType, ph.nested(tokens.keys.length)); requiredType, ph.nested(tokens.keys.length));
int size = list.size(); int size = list.size();
if (index >= size && index < this.autoGrowCollectionLimit) { if (index >= size && index < this.autoGrowCollectionLimit) {
@ -347,9 +325,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
list.add(null); list.add(null);
} }
catch (NullPointerException ex) { catch (NullPointerException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Cannot set element with index " + index + " in List of size " + "Cannot set element with index " + index + " in List of size " +
size + ", accessed using property path '" + propertyName + size + ", accessed using property path '" + tokens.canonicalName +
"': List does not support filling up gaps with null elements"); "': List does not support filling up gaps with null elements");
} }
} }
@ -360,51 +338,88 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
list.set(index, convertedValue); list.set(index, convertedValue);
} }
catch (IndexOutOfBoundsException ex) { catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Invalid list index in property path '" + propertyName + "'", ex); "Invalid list index in property path '" + tokens.canonicalName + "'", ex);
} }
} }
} }
else if (propValue instanceof Map) { else if (propValue instanceof Map) {
PropertyHandler ph = getLocalPropertyHandler(actualName); PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length); Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
Class<?> mapValueType = ph.getMapValueType(tokens.keys.length); Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
Map<Object, Object> map = (Map<Object, Object>) propValue; Map<Object, Object> map = (Map<Object, Object>) propValue;
// IMPORTANT: Do not pass full property name in here - property editors // IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values. // must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);
Object oldValue = null; Object oldValue = null;
if (isExtractOldValueForEditor()) { if (isExtractOldValueForEditor()) {
oldValue = map.get(convertedMapKey); oldValue = map.get(convertedMapKey);
} }
// Pass full property name and old value in here, since we want full // Pass full property name and old value in here, since we want full
// conversion ability for map values. // conversion ability for map values.
Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
mapValueType, ph.nested(tokens.keys.length)); mapValueType, ph.nested(tokens.keys.length));
map.put(convertedMapKey, convertedMapValue); map.put(convertedMapKey, convertedMapValue);
} }
else { else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Property referenced in indexed property path '" + propertyName + "Property referenced in indexed property path '" + tokens.canonicalName +
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
} }
} }
private Object getPropertyHoldingValue(PropertyTokenHolder tokens) {
// 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 + tokens.canonicalName,
"Cannot access indexed value in property referenced " +
"in indexed property path '" + tokens.canonicalName + "'", ex);
}
if (propValue == null) {
// null map value case
if (isAutoGrowNestedPaths()) {
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
propValue = setDefaultValue(getterTokens);
}
else { else {
PropertyHandler ph = getLocalPropertyHandler(actualName); throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Cannot access indexed value in property referenced " +
"in indexed property path '" + tokens.canonicalName + "': returned null");
}
}
return propValue;
}
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
if (ph == null || !ph.isWritable()) { if (ph == null || !ph.isWritable()) {
if (pv.isOptional()) { if (pv.isOptional()) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Ignoring optional value for property '" + actualName + logger.debug("Ignoring optional value for property '" + tokens.actualName +
"' - property not found on bean class [" + getRootClass().getName() + "]"); "' - property not found on bean class [" + getRootClass().getName() + "]");
} }
return; return;
} }
else { else {
throw createNotWritablePropertyException(propertyName); throw createNotWritablePropertyException(tokens.canonicalName);
} }
} }
Object oldValue = null; Object oldValue = null;
try { try {
Object originalValue = pv.getValue(); Object originalValue = pv.getValue();
@ -424,12 +439,12 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Could not read previous value of property '" + logger.debug("Could not read previous value of property '" +
this.nestedPath + propertyName + "'", ex); this.nestedPath + tokens.canonicalName + "'", ex);
} }
} }
} }
valueToApply = convertForProperty( valueToApply = convertForProperty(
propertyName, oldValue, originalValue, ph.toTypeDescriptor()); tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
} }
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
} }
@ -439,8 +454,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
throw ex; throw ex;
} }
catch (InvocationTargetException ex) { catch (InvocationTargetException ex) {
PropertyChangeEvent propertyChangeEvent = PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
if (ex.getTargetException() instanceof ClassCastException) { if (ex.getTargetException() instanceof ClassCastException) {
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
} }
@ -454,12 +469,11 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
} }
} }
catch (Exception ex) { catch (Exception ex) {
PropertyChangeEvent pce = PropertyChangeEvent pce = new PropertyChangeEvent(
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
throw new MethodInvocationException(pce, ex); throw new MethodInvocationException(pce, ex);
} }
} }
}
@Override @Override
public Class<?> getPropertyType(String propertyName) throws BeansException { public Class<?> getPropertyType(String propertyName) throws BeansException {
@ -958,9 +972,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
} }
/**
* Handle a given property.
*/
protected abstract static class PropertyHandler { protected abstract static class PropertyHandler {
private final Class<?> propertyType; private final Class<?> propertyType;