Skip to content

Commit d77ab67

Browse files
committed
AbstractNestablePropertyAccessor's setPropertyValue refactored into several delegate methods
Issue: SPR-15053 (cherry picked from commit 64d6561)
1 parent dd2b6a0 commit d77ab67

File tree

1 file changed

+176
-165
lines changed

1 file changed

+176
-165
lines changed

spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java

Lines changed: 176 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -284,198 +284,212 @@ public void setPropertyValue(PropertyValue pv) throws BeansException {
284284
}
285285
}
286286

287-
@SuppressWarnings("unchecked")
288287
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
289-
String propertyName = tokens.canonicalName;
290-
String actualName = tokens.actualName;
291-
292288
if (tokens.keys != null) {
293-
// Apply indexes and map keys: fetch value for all keys but the last one.
294-
PropertyTokenHolder getterTokens = new PropertyTokenHolder();
295-
getterTokens.canonicalName = tokens.canonicalName;
296-
getterTokens.actualName = tokens.actualName;
297-
getterTokens.keys = new String[tokens.keys.length - 1];
298-
System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
299-
Object propValue;
289+
processKeyedProperty(tokens, pv);
290+
}
291+
else {
292+
processLocalProperty(tokens, pv);
293+
}
294+
}
295+
296+
@SuppressWarnings("unchecked")
297+
private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
298+
Object propValue = getPropertyHoldingValue(tokens);
299+
String lastKey = tokens.keys[tokens.keys.length - 1];
300+
301+
if (propValue.getClass().isArray()) {
302+
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
303+
Class<?> requiredType = propValue.getClass().getComponentType();
304+
int arrayIndex = Integer.parseInt(lastKey);
305+
Object oldValue = null;
300306
try {
301-
propValue = getPropertyValue(getterTokens);
302-
}
303-
catch (NotReadablePropertyException ex) {
304-
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
305-
"Cannot access indexed value in property referenced " +
306-
"in indexed property path '" + propertyName + "'", ex);
307-
}
308-
// Set value for last key.
309-
String key = tokens.keys[tokens.keys.length - 1];
310-
if (propValue == null) {
311-
// null map value case
312-
if (isAutoGrowNestedPaths()) {
313-
// TODO: cleanup, this is pretty hacky
314-
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
315-
getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
316-
propValue = setDefaultValue(getterTokens);
307+
if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
308+
oldValue = Array.get(propValue, arrayIndex);
317309
}
318-
else {
319-
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
320-
"Cannot access indexed value in property referenced " +
321-
"in indexed property path '" + propertyName + "': returned null");
310+
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
311+
requiredType, ph.nested(tokens.keys.length));
312+
int length = Array.getLength(propValue);
313+
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
314+
Class<?> componentType = propValue.getClass().getComponentType();
315+
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
316+
System.arraycopy(propValue, 0, newArray, 0, length);
317+
setPropertyValue(tokens.actualName, newArray);
318+
propValue = getPropertyValue(tokens.actualName);
322319
}
320+
Array.set(propValue, arrayIndex, convertedValue);
323321
}
324-
if (propValue.getClass().isArray()) {
325-
PropertyHandler ph = getLocalPropertyHandler(actualName);
326-
Class<?> requiredType = propValue.getClass().getComponentType();
327-
int arrayIndex = Integer.parseInt(key);
328-
Object oldValue = null;
329-
try {
330-
if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
331-
oldValue = Array.get(propValue, arrayIndex);
322+
catch (IndexOutOfBoundsException ex) {
323+
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
324+
"Invalid array index in property path '" + tokens.canonicalName + "'", ex);
325+
}
326+
}
327+
328+
else if (propValue instanceof List) {
329+
PropertyHandler ph = getPropertyHandler(tokens.actualName);
330+
Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
331+
List<Object> list = (List<Object>) propValue;
332+
int index = Integer.parseInt(lastKey);
333+
Object oldValue = null;
334+
if (isExtractOldValueForEditor() && index < list.size()) {
335+
oldValue = list.get(index);
336+
}
337+
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
338+
requiredType, ph.nested(tokens.keys.length));
339+
int size = list.size();
340+
if (index >= size && index < this.autoGrowCollectionLimit) {
341+
for (int i = size; i < index; i++) {
342+
try {
343+
list.add(null);
332344
}
333-
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
334-
requiredType, ph.nested(tokens.keys.length));
335-
int length = Array.getLength(propValue);
336-
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
337-
Class<?> componentType = propValue.getClass().getComponentType();
338-
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
339-
System.arraycopy(propValue, 0, newArray, 0, length);
340-
setPropertyValue(actualName, newArray);
341-
propValue = getPropertyValue(actualName);
345+
catch (NullPointerException ex) {
346+
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
347+
"Cannot set element with index " + index + " in List of size " +
348+
size + ", accessed using property path '" + tokens.canonicalName +
349+
"': List does not support filling up gaps with null elements");
342350
}
343-
Array.set(propValue, arrayIndex, convertedValue);
344-
}
345-
catch (IndexOutOfBoundsException ex) {
346-
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
347-
"Invalid array index in property path '" + propertyName + "'", ex);
348351
}
352+
list.add(convertedValue);
349353
}
350-
else if (propValue instanceof List) {
351-
PropertyHandler ph = getPropertyHandler(actualName);
352-
Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
353-
List<Object> list = (List<Object>) propValue;
354-
int index = Integer.parseInt(key);
355-
Object oldValue = null;
356-
if (isExtractOldValueForEditor() && index < list.size()) {
357-
oldValue = list.get(index);
358-
}
359-
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
360-
requiredType, ph.nested(tokens.keys.length));
361-
int size = list.size();
362-
if (index >= size && index < this.autoGrowCollectionLimit) {
363-
for (int i = size; i < index; i++) {
364-
try {
365-
list.add(null);
366-
}
367-
catch (NullPointerException ex) {
368-
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
369-
"Cannot set element with index " + index + " in List of size " +
370-
size + ", accessed using property path '" + propertyName +
371-
"': List does not support filling up gaps with null elements");
372-
}
373-
}
374-
list.add(convertedValue);
354+
else {
355+
try {
356+
list.set(index, convertedValue);
375357
}
376-
else {
377-
try {
378-
list.set(index, convertedValue);
379-
}
380-
catch (IndexOutOfBoundsException ex) {
381-
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
382-
"Invalid list index in property path '" + propertyName + "'", ex);
383-
}
358+
catch (IndexOutOfBoundsException ex) {
359+
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
360+
"Invalid list index in property path '" + tokens.canonicalName + "'", ex);
384361
}
385362
}
386-
else if (propValue instanceof Map) {
387-
PropertyHandler ph = getLocalPropertyHandler(actualName);
388-
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
389-
Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
390-
Map<Object, Object> map = (Map<Object, Object>) propValue;
391-
// IMPORTANT: Do not pass full property name in here - property editors
392-
// must not kick in for map keys but rather only for map values.
393-
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
394-
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
395-
Object oldValue = null;
396-
if (isExtractOldValueForEditor()) {
397-
oldValue = map.get(convertedMapKey);
363+
}
364+
365+
else if (propValue instanceof Map) {
366+
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
367+
Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
368+
Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
369+
Map<Object, Object> map = (Map<Object, Object>) propValue;
370+
// IMPORTANT: Do not pass full property name in here - property editors
371+
// must not kick in for map keys but rather only for map values.
372+
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
373+
Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);
374+
Object oldValue = null;
375+
if (isExtractOldValueForEditor()) {
376+
oldValue = map.get(convertedMapKey);
377+
}
378+
// Pass full property name and old value in here, since we want full
379+
// conversion ability for map values.
380+
Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
381+
mapValueType, ph.nested(tokens.keys.length));
382+
map.put(convertedMapKey, convertedMapValue);
383+
}
384+
385+
else {
386+
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
387+
"Property referenced in indexed property path '" + tokens.canonicalName +
388+
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
389+
}
390+
}
391+
392+
private Object getPropertyHoldingValue(PropertyTokenHolder tokens) {
393+
// Apply indexes and map keys: fetch value for all keys but the last one.
394+
PropertyTokenHolder getterTokens = new PropertyTokenHolder();
395+
getterTokens.canonicalName = tokens.canonicalName;
396+
getterTokens.actualName = tokens.actualName;
397+
getterTokens.keys = new String[tokens.keys.length - 1];
398+
System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
399+
400+
Object propValue;
401+
try {
402+
propValue = getPropertyValue(getterTokens);
403+
}
404+
catch (NotReadablePropertyException ex) {
405+
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
406+
"Cannot access indexed value in property referenced " +
407+
"in indexed property path '" + tokens.canonicalName + "'", ex);
408+
}
409+
410+
if (propValue == null) {
411+
// null map value case
412+
if (isAutoGrowNestedPaths()) {
413+
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
414+
getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
415+
propValue = setDefaultValue(getterTokens);
416+
}
417+
else {
418+
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName,
419+
"Cannot access indexed value in property referenced " +
420+
"in indexed property path '" + tokens.canonicalName + "': returned null");
421+
}
422+
}
423+
return propValue;
424+
}
425+
426+
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
427+
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
428+
if (ph == null || !ph.isWritable()) {
429+
if (pv.isOptional()) {
430+
if (logger.isDebugEnabled()) {
431+
logger.debug("Ignoring optional value for property '" + tokens.actualName +
432+
"' - property not found on bean class [" + getRootClass().getName() + "]");
398433
}
399-
// Pass full property name and old value in here, since we want full
400-
// conversion ability for map values.
401-
Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
402-
mapValueType, ph.nested(tokens.keys.length));
403-
map.put(convertedMapKey, convertedMapValue);
434+
return;
404435
}
405436
else {
406-
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
407-
"Property referenced in indexed property path '" + propertyName +
408-
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
437+
throw createNotWritablePropertyException(tokens.canonicalName);
409438
}
410439
}
411440

412-
else {
413-
PropertyHandler ph = getLocalPropertyHandler(actualName);
414-
if (ph == null || !ph.isWritable()) {
415-
if (pv.isOptional()) {
416-
if (logger.isDebugEnabled()) {
417-
logger.debug("Ignoring optional value for property '" + actualName +
418-
"' - property not found on bean class [" + getRootClass().getName() + "]");
419-
}
420-
return;
441+
Object oldValue = null;
442+
try {
443+
Object originalValue = pv.getValue();
444+
Object valueToApply = originalValue;
445+
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
446+
if (pv.isConverted()) {
447+
valueToApply = pv.getConvertedValue();
421448
}
422449
else {
423-
throw createNotWritablePropertyException(propertyName);
424-
}
425-
}
426-
Object oldValue = null;
427-
try {
428-
Object originalValue = pv.getValue();
429-
Object valueToApply = originalValue;
430-
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
431-
if (pv.isConverted()) {
432-
valueToApply = pv.getConvertedValue();
433-
}
434-
else {
435-
if (isExtractOldValueForEditor() && ph.isReadable()) {
436-
try {
437-
oldValue = ph.getValue();
450+
if (isExtractOldValueForEditor() && ph.isReadable()) {
451+
try {
452+
oldValue = ph.getValue();
453+
}
454+
catch (Exception ex) {
455+
if (ex instanceof PrivilegedActionException) {
456+
ex = ((PrivilegedActionException) ex).getException();
438457
}
439-
catch (Exception ex) {
440-
if (ex instanceof PrivilegedActionException) {
441-
ex = ((PrivilegedActionException) ex).getException();
442-
}
443-
if (logger.isDebugEnabled()) {
444-
logger.debug("Could not read previous value of property '" +
445-
this.nestedPath + propertyName + "'", ex);
446-
}
458+
if (logger.isDebugEnabled()) {
459+
logger.debug("Could not read previous value of property '" +
460+
this.nestedPath + tokens.canonicalName + "'", ex);
447461
}
448462
}
449-
valueToApply = convertForProperty(
450-
propertyName, oldValue, originalValue, ph.toTypeDescriptor());
451463
}
452-
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
464+
valueToApply = convertForProperty(
465+
tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
453466
}
454-
ph.setValue(this.wrappedObject, valueToApply);
467+
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
455468
}
456-
catch (TypeMismatchException ex) {
457-
throw ex;
469+
ph.setValue(this.wrappedObject, valueToApply);
470+
}
471+
catch (TypeMismatchException ex) {
472+
throw ex;
473+
}
474+
catch (InvocationTargetException ex) {
475+
PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
476+
this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
477+
if (ex.getTargetException() instanceof ClassCastException) {
478+
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
458479
}
459-
catch (InvocationTargetException ex) {
460-
PropertyChangeEvent propertyChangeEvent =
461-
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
462-
if (ex.getTargetException() instanceof ClassCastException) {
463-
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
464-
}
465-
else {
466-
Throwable cause = ex.getTargetException();
467-
if (cause instanceof UndeclaredThrowableException) {
468-
// May happen e.g. with Groovy-generated methods
469-
cause = cause.getCause();
470-
}
471-
throw new MethodInvocationException(propertyChangeEvent, cause);
480+
else {
481+
Throwable cause = ex.getTargetException();
482+
if (cause instanceof UndeclaredThrowableException) {
483+
// May happen e.g. with Groovy-generated methods
484+
cause = cause.getCause();
472485
}
486+
throw new MethodInvocationException(propertyChangeEvent, cause);
473487
}
474-
catch (Exception ex) {
475-
PropertyChangeEvent pce =
476-
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
477-
throw new MethodInvocationException(pce, ex);
478-
}
488+
}
489+
catch (Exception ex) {
490+
PropertyChangeEvent pce = new PropertyChangeEvent(
491+
this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
492+
throw new MethodInvocationException(pce, ex);
479493
}
480494
}
481495

@@ -973,9 +987,6 @@ public String toString() {
973987
}
974988

975989

976-
/**
977-
* Handle a given property.
978-
*/
979990
protected abstract static class PropertyHandler {
980991

981992
private final Class<?> propertyType;

0 commit comments

Comments
 (0)