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,198 +266,212 @@ 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; else {
getterTokens.actualName = tokens.actualName; processLocalProperty(tokens, pv);
getterTokens.keys = new String[tokens.keys.length - 1]; }
System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); }
Object propValue;
@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()) {
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
Class<?> requiredType = propValue.getClass().getComponentType();
int arrayIndex = Integer.parseInt(lastKey);
Object oldValue = null;
try { try {
propValue = getPropertyValue(getterTokens); if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
} oldValue = Array.get(propValue, arrayIndex);
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 { Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
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)); requiredType, ph.nested(tokens.keys.length));
int size = list.size(); int length = Array.getLength(propValue);
if (index >= size && index < this.autoGrowCollectionLimit) { if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
for (int i = size; i < index; i++) { Class<?> componentType = propValue.getClass().getComponentType();
try { Object newArray = Array.newInstance(componentType, arrayIndex + 1);
list.add(null); System.arraycopy(propValue, 0, newArray, 0, length);
} setPropertyValue(tokens.actualName, newArray);
catch (NullPointerException ex) { propValue = getPropertyValue(tokens.actualName);
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);
}
} }
Array.set(propValue, arrayIndex, convertedValue);
} }
else if (propValue instanceof Map) { catch (IndexOutOfBoundsException ex) {
PropertyHandler ph = getLocalPropertyHandler(actualName); throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length); "Invalid array index in property path '" + tokens.canonicalName + "'", ex);
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 { else if (propValue instanceof List) {
PropertyHandler ph = getLocalPropertyHandler(actualName); PropertyHandler ph = getPropertyHandler(tokens.actualName);
if (ph == null || !ph.isWritable()) { Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
if (pv.isOptional()) { List<Object> list = (List<Object>) propValue;
if (logger.isDebugEnabled()) { int index = Integer.parseInt(lastKey);
logger.debug("Ignoring optional value for property '" + actualName + Object oldValue = null;
"' - property not found on bean class [" + getRootClass().getName() + "]"); if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index);
}
Object convertedValue = convertIfNecessary(tokens.canonicalName, 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 + tokens.canonicalName,
"Cannot set element with index " + index + " in List of size " +
size + ", accessed using property path '" + tokens.canonicalName +
"': List does not support filling up gaps with null elements");
} }
return;
} }
else { list.add(convertedValue);
throw createNotWritablePropertyException(propertyName); }
else {
try {
list.set(index, convertedValue);
}
catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Invalid list index in property path '" + tokens.canonicalName + "'", ex);
} }
} }
}
else if (propValue instanceof Map) {
PropertyHandler ph = getLocalPropertyHandler(tokens.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, lastKey, mapKeyType, typeDescriptor);
Object oldValue = null; Object oldValue = null;
try { if (isExtractOldValueForEditor()) {
Object originalValue = pv.getValue(); oldValue = map.get(convertedMapKey);
Object valueToApply = originalValue; }
if (!Boolean.FALSE.equals(pv.conversionNecessary)) { // Pass full property name and old value in here, since we want full
if (pv.isConverted()) { // conversion ability for map values.
valueToApply = pv.getConvertedValue(); Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
} mapValueType, ph.nested(tokens.keys.length));
else { map.put(convertedMapKey, convertedMapValue);
if (isExtractOldValueForEditor() && ph.isReadable()) { }
try {
oldValue = ph.getValue(); else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Property referenced in indexed property path '" + tokens.canonicalName +
"' 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 {
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 (pv.isOptional()) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring optional value for property '" + tokens.actualName +
"' - property not found on bean class [" + getRootClass().getName() + "]");
}
return;
}
else {
throw createNotWritablePropertyException(tokens.canonicalName);
}
}
Object oldValue = null;
try {
Object originalValue = pv.getValue();
Object valueToApply = originalValue;
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
if (pv.isConverted()) {
valueToApply = pv.getConvertedValue();
}
else {
if (isExtractOldValueForEditor() && ph.isReadable()) {
try {
oldValue = ph.getValue();
}
catch (Exception ex) {
if (ex instanceof PrivilegedActionException) {
ex = ((PrivilegedActionException) ex).getException();
} }
catch (Exception ex) { if (logger.isDebugEnabled()) {
if (ex instanceof PrivilegedActionException) { logger.debug("Could not read previous value of property '" +
ex = ((PrivilegedActionException) ex).getException(); this.nestedPath + tokens.canonicalName + "'", ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Could not read previous value of property '" +
this.nestedPath + propertyName + "'", ex);
}
} }
} }
valueToApply = convertForProperty(
propertyName, oldValue, originalValue, ph.toTypeDescriptor());
} }
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); valueToApply = convertForProperty(
tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
} }
ph.setValue(this.wrappedObject, valueToApply); pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
} }
catch (TypeMismatchException ex) { ph.setValue(this.wrappedObject, valueToApply);
throw ex; }
catch (TypeMismatchException ex) {
throw ex;
}
catch (InvocationTargetException ex) {
PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
if (ex.getTargetException() instanceof ClassCastException) {
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
} }
catch (InvocationTargetException ex) { else {
PropertyChangeEvent propertyChangeEvent = Throwable cause = ex.getTargetException();
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); if (cause instanceof UndeclaredThrowableException) {
if (ex.getTargetException() instanceof ClassCastException) { // May happen e.g. with Groovy-generated methods
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); cause = cause.getCause();
}
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);
} }
throw new MethodInvocationException(propertyChangeEvent, cause);
} }
catch (Exception ex) { }
PropertyChangeEvent pce = catch (Exception ex) {
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); PropertyChangeEvent pce = new PropertyChangeEvent(
throw new MethodInvocationException(pce, ex); this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
} throw new MethodInvocationException(pce, ex);
} }
} }
@ -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;