Skip to content

Commit 64d6561

Browse files
committed
AbstractNestablePropertyAccessor's setPropertyValue refactored into several delegate methods
Issue: SPR-15053
1 parent 47be2d8 commit 64d6561

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
@@ -266,198 +266,212 @@ public void setPropertyValue(PropertyValue pv) throws BeansException {
266266
}
267267
}
268268

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

394-
else {
395-
PropertyHandler ph = getLocalPropertyHandler(actualName);
396-
if (ph == null || !ph.isWritable()) {
397-
if (pv.isOptional()) {
398-
if (logger.isDebugEnabled()) {
399-
logger.debug("Ignoring optional value for property '" + actualName +
400-
"' - property not found on bean class [" + getRootClass().getName() + "]");
401-
}
402-
return;
423+
Object oldValue = null;
424+
try {
425+
Object originalValue = pv.getValue();
426+
Object valueToApply = originalValue;
427+
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
428+
if (pv.isConverted()) {
429+
valueToApply = pv.getConvertedValue();
403430
}
404431
else {
405-
throw createNotWritablePropertyException(propertyName);
406-
}
407-
}
408-
Object oldValue = null;
409-
try {
410-
Object originalValue = pv.getValue();
411-
Object valueToApply = originalValue;
412-
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
413-
if (pv.isConverted()) {
414-
valueToApply = pv.getConvertedValue();
415-
}
416-
else {
417-
if (isExtractOldValueForEditor() && ph.isReadable()) {
418-
try {
419-
oldValue = ph.getValue();
432+
if (isExtractOldValueForEditor() && ph.isReadable()) {
433+
try {
434+
oldValue = ph.getValue();
435+
}
436+
catch (Exception ex) {
437+
if (ex instanceof PrivilegedActionException) {
438+
ex = ((PrivilegedActionException) ex).getException();
420439
}
421-
catch (Exception ex) {
422-
if (ex instanceof PrivilegedActionException) {
423-
ex = ((PrivilegedActionException) ex).getException();
424-
}
425-
if (logger.isDebugEnabled()) {
426-
logger.debug("Could not read previous value of property '" +
427-
this.nestedPath + propertyName + "'", ex);
428-
}
440+
if (logger.isDebugEnabled()) {
441+
logger.debug("Could not read previous value of property '" +
442+
this.nestedPath + tokens.canonicalName + "'", ex);
429443
}
430444
}
431-
valueToApply = convertForProperty(
432-
propertyName, oldValue, originalValue, ph.toTypeDescriptor());
433445
}
434-
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
446+
valueToApply = convertForProperty(
447+
tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
435448
}
436-
ph.setValue(this.wrappedObject, valueToApply);
449+
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
437450
}
438-
catch (TypeMismatchException ex) {
439-
throw ex;
451+
ph.setValue(this.wrappedObject, valueToApply);
452+
}
453+
catch (TypeMismatchException ex) {
454+
throw ex;
455+
}
456+
catch (InvocationTargetException ex) {
457+
PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
458+
this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
459+
if (ex.getTargetException() instanceof ClassCastException) {
460+
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
440461
}
441-
catch (InvocationTargetException ex) {
442-
PropertyChangeEvent propertyChangeEvent =
443-
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
444-
if (ex.getTargetException() instanceof ClassCastException) {
445-
throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
446-
}
447-
else {
448-
Throwable cause = ex.getTargetException();
449-
if (cause instanceof UndeclaredThrowableException) {
450-
// May happen e.g. with Groovy-generated methods
451-
cause = cause.getCause();
452-
}
453-
throw new MethodInvocationException(propertyChangeEvent, cause);
462+
else {
463+
Throwable cause = ex.getTargetException();
464+
if (cause instanceof UndeclaredThrowableException) {
465+
// May happen e.g. with Groovy-generated methods
466+
cause = cause.getCause();
454467
}
468+
throw new MethodInvocationException(propertyChangeEvent, cause);
455469
}
456-
catch (Exception ex) {
457-
PropertyChangeEvent pce =
458-
new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue());
459-
throw new MethodInvocationException(pce, ex);
460-
}
470+
}
471+
catch (Exception ex) {
472+
PropertyChangeEvent pce = new PropertyChangeEvent(
473+
this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
474+
throw new MethodInvocationException(pce, ex);
461475
}
462476
}
463477

@@ -958,9 +972,6 @@ public String toString() {
958972
}
959973

960974

961-
/**
962-
* Handle a given property.
963-
*/
964975
protected abstract static class PropertyHandler {
965976

966977
private final Class<?> propertyType;

0 commit comments

Comments
 (0)