|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2013 the original author or authors. |
|
|
|
* Copyright 2002-2014 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -34,7 +34,7 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* An Indexer can index into some proceeding structure to access a particular piece of it. |
|
|
|
* An Indexer can index into some proceeding structure to access a particular piece of it. |
|
|
|
* Supported structures are: strings/collections (lists/sets)/arrays |
|
|
|
* Supported structures are: strings / collections (lists/sets) / arrays. |
|
|
|
* |
|
|
|
* |
|
|
|
* @author Andy Clement |
|
|
|
* @author Andy Clement |
|
|
|
* @author Phillip Webb |
|
|
|
* @author Phillip Webb |
|
|
|
@ -88,6 +88,206 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { |
|
|
|
|
|
|
|
TypedValue context = state.getActiveContextObject(); |
|
|
|
|
|
|
|
Object targetObject = context.getValue(); |
|
|
|
|
|
|
|
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor(); |
|
|
|
|
|
|
|
TypedValue indexValue = null; |
|
|
|
|
|
|
|
Object index = null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This first part of the if clause prevents a 'double dereference' of
|
|
|
|
|
|
|
|
// the property (SPR-5847)
|
|
|
|
|
|
|
|
if (targetObject instanceof Map && (this.children[0] instanceof PropertyOrFieldReference)) { |
|
|
|
|
|
|
|
PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0]; |
|
|
|
|
|
|
|
index = reference.getName(); |
|
|
|
|
|
|
|
indexValue = new TypedValue(index); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
// In case the map key is unqualified, we want it evaluated against
|
|
|
|
|
|
|
|
// the root object so temporarily push that on whilst evaluating the key
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
state.pushActiveContextObject(state.getRootContextObject()); |
|
|
|
|
|
|
|
indexValue = this.children[0].getValueInternal(state); |
|
|
|
|
|
|
|
index = indexValue.getValue(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
state.popActiveContextObject(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Indexing into a Map
|
|
|
|
|
|
|
|
if (targetObject instanceof Map) { |
|
|
|
|
|
|
|
Object key = index; |
|
|
|
|
|
|
|
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) { |
|
|
|
|
|
|
|
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return new MapIndexingValueRef(state.getTypeConverter(), (Map<?, ?>) targetObject, key, |
|
|
|
|
|
|
|
targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (targetObject == null) { |
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if the object is something that looks indexable by an integer,
|
|
|
|
|
|
|
|
// attempt to treat the index value as a number
|
|
|
|
|
|
|
|
if (targetObject.getClass().isArray() || targetObject instanceof Collection || targetObject instanceof String) { |
|
|
|
|
|
|
|
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); |
|
|
|
|
|
|
|
if (targetObject.getClass().isArray()) { |
|
|
|
|
|
|
|
return new ArrayIndexingValueRef(state.getTypeConverter(), targetObject, idx, targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (targetObject instanceof Collection) { |
|
|
|
|
|
|
|
return new CollectionIndexingValueRef((Collection<?>) targetObject, idx, targetObjectTypeDescriptor, |
|
|
|
|
|
|
|
state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(), |
|
|
|
|
|
|
|
state.getConfiguration().getMaximumAutoGrowSize()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (targetObject instanceof String) { |
|
|
|
|
|
|
|
return new StringIndexingLValue((String) targetObject, idx, targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Try and treat the index value as a property of the context object
|
|
|
|
|
|
|
|
// TODO could call the conversion service to convert the value to a String
|
|
|
|
|
|
|
|
if (indexValue.getTypeDescriptor().getType() == String.class) { |
|
|
|
|
|
|
|
return new PropertyIndexingValueRef(targetObject, (String) indexValue.getValue(), |
|
|
|
|
|
|
|
state.getEvaluationContext(), targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, |
|
|
|
|
|
|
|
targetObjectTypeDescriptor.toString()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String toStringAST() { |
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
|
|
|
|
sb.append("["); |
|
|
|
|
|
|
|
for (int i = 0; i < getChildCount(); i++) { |
|
|
|
|
|
|
|
if (i > 0) { |
|
|
|
|
|
|
|
sb.append(","); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sb.append(getChild(i).toStringAST()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sb.append("]"); |
|
|
|
|
|
|
|
return sb.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void setArrayElement(TypeConverter converter, Object ctx, int idx, Object newValue, |
|
|
|
|
|
|
|
Class<?> arrayComponentType) throws EvaluationException { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (arrayComponentType == Integer.TYPE) { |
|
|
|
|
|
|
|
int[] array = (int[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Integer) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Integer.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Boolean.TYPE) { |
|
|
|
|
|
|
|
boolean[] array = (boolean[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Boolean) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Boolean.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Character.TYPE) { |
|
|
|
|
|
|
|
char[] array = (char[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Character) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Character.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Long.TYPE) { |
|
|
|
|
|
|
|
long[] array = (long[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Long) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Long.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Short.TYPE) { |
|
|
|
|
|
|
|
short[] array = (short[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Short) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Short.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Double.TYPE) { |
|
|
|
|
|
|
|
double[] array = (double[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Double) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Double.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Float.TYPE) { |
|
|
|
|
|
|
|
float[] array = (float[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Float) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Float.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Byte.TYPE) { |
|
|
|
|
|
|
|
byte[] array = (byte[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Byte) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Byte.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
Object[] array = (Object[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(arrayComponentType)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException { |
|
|
|
|
|
|
|
Class<?> arrayComponentType = ctx.getClass().getComponentType(); |
|
|
|
|
|
|
|
if (arrayComponentType == Integer.TYPE) { |
|
|
|
|
|
|
|
int[] array = (int[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Boolean.TYPE) { |
|
|
|
|
|
|
|
boolean[] array = (boolean[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Character.TYPE) { |
|
|
|
|
|
|
|
char[] array = (char[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Long.TYPE) { |
|
|
|
|
|
|
|
long[] array = (long[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Short.TYPE) { |
|
|
|
|
|
|
|
short[] array = (short[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Double.TYPE) { |
|
|
|
|
|
|
|
double[] array = (double[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Float.TYPE) { |
|
|
|
|
|
|
|
float[] array = (float[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Byte.TYPE) { |
|
|
|
|
|
|
|
byte[] array = (byte[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
Object[] array = (Object[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException { |
|
|
|
|
|
|
|
if (index > arrayLength) { |
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, |
|
|
|
|
|
|
|
arrayLength, index); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class ArrayIndexingValueRef implements ValueRef { |
|
|
|
private class ArrayIndexingValueRef implements ValueRef { |
|
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter; |
|
|
|
private final TypeConverter typeConverter; |
|
|
|
@ -127,7 +327,7 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"}) |
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"}) |
|
|
|
private class MapIndexingValueRef implements ValueRef { |
|
|
|
private static class MapIndexingValueRef implements ValueRef { |
|
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter; |
|
|
|
private final TypeConverter typeConverter; |
|
|
|
|
|
|
|
|
|
|
|
@ -137,16 +337,13 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor mapEntryTypeDescriptor; |
|
|
|
private final TypeDescriptor mapEntryTypeDescriptor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, TypeDescriptor mapEntryTypeDescriptor) { |
|
|
|
MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, |
|
|
|
|
|
|
|
TypeDescriptor mapEntryTypeDescriptor) { |
|
|
|
|
|
|
|
this.typeConverter = typeConverter; |
|
|
|
this.typeConverter = typeConverter; |
|
|
|
this.map = map; |
|
|
|
this.map = map; |
|
|
|
this.key = key; |
|
|
|
this.key = key; |
|
|
|
this.mapEntryTypeDescriptor = mapEntryTypeDescriptor; |
|
|
|
this.mapEntryTypeDescriptor = mapEntryTypeDescriptor; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public TypedValue getValue() { |
|
|
|
public TypedValue getValue() { |
|
|
|
Object value = this.map.get(this.key); |
|
|
|
Object value = this.map.get(this.key); |
|
|
|
@ -180,7 +377,6 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor targetObjectTypeDescriptor; |
|
|
|
private final TypeDescriptor targetObjectTypeDescriptor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext, |
|
|
|
public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext, |
|
|
|
TypeDescriptor targetObjectTypeDescriptor) { |
|
|
|
TypeDescriptor targetObjectTypeDescriptor) { |
|
|
|
this.targetObject = targetObject; |
|
|
|
this.targetObject = targetObject; |
|
|
|
@ -194,15 +390,14 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
public TypedValue getValue() { |
|
|
|
public TypedValue getValue() { |
|
|
|
Class<?> targetObjectRuntimeClass = getObjectClass(this.targetObject); |
|
|
|
Class<?> targetObjectRuntimeClass = getObjectClass(this.targetObject); |
|
|
|
try { |
|
|
|
try { |
|
|
|
if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) && Indexer.this.cachedReadTargetType != null && |
|
|
|
if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) && |
|
|
|
|
|
|
|
Indexer.this.cachedReadTargetType != null && |
|
|
|
Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) { |
|
|
|
Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) { |
|
|
|
// it is OK to use the cached accessor
|
|
|
|
// It is OK to use the cached accessor
|
|
|
|
return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name); |
|
|
|
return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry( |
|
|
|
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry( |
|
|
|
targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors()); |
|
|
|
targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors()); |
|
|
|
|
|
|
|
|
|
|
|
if (accessorsToTry != null) { |
|
|
|
if (accessorsToTry != null) { |
|
|
|
for (PropertyAccessor accessor : accessorsToTry) { |
|
|
|
for (PropertyAccessor accessor : accessorsToTry) { |
|
|
|
if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { |
|
|
|
if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { |
|
|
|
@ -230,9 +425,10 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
public void setValue(Object newValue) { |
|
|
|
public void setValue(Object newValue) { |
|
|
|
Class<?> contextObjectClass = getObjectClass(this.targetObject); |
|
|
|
Class<?> contextObjectClass = getObjectClass(this.targetObject); |
|
|
|
try { |
|
|
|
try { |
|
|
|
if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) && Indexer.this.cachedWriteTargetType != null && |
|
|
|
if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) && |
|
|
|
|
|
|
|
Indexer.this.cachedWriteTargetType != null && |
|
|
|
Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) { |
|
|
|
Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) { |
|
|
|
// it is OK to use the cached accessor
|
|
|
|
// It is OK to use the cached accessor
|
|
|
|
Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue); |
|
|
|
Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -270,7 +466,7 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
|
|
|
|
|
|
|
|
private final int index; |
|
|
|
private final int index; |
|
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor collectionEntryTypeDescriptor; |
|
|
|
private final TypeDescriptor collectionEntryDescriptor; |
|
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter; |
|
|
|
private final TypeConverter typeConverter; |
|
|
|
|
|
|
|
|
|
|
|
@ -278,12 +474,11 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
|
|
|
|
|
|
|
|
private final int maximumSize; |
|
|
|
private final int maximumSize; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor, |
|
|
|
CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor, |
|
|
|
|
|
|
|
TypeConverter typeConverter, boolean growCollection, int maximumSize) { |
|
|
|
TypeConverter typeConverter, boolean growCollection, int maximumSize) { |
|
|
|
this.collection = collection; |
|
|
|
this.collection = collection; |
|
|
|
this.index = index; |
|
|
|
this.index = index; |
|
|
|
this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor; |
|
|
|
this.collectionEntryDescriptor = collectionEntryTypeDescriptor; |
|
|
|
this.typeConverter = typeConverter; |
|
|
|
this.typeConverter = typeConverter; |
|
|
|
this.growCollection = growCollection; |
|
|
|
this.growCollection = growCollection; |
|
|
|
this.maximumSize = maximumSize; |
|
|
|
this.maximumSize = maximumSize; |
|
|
|
@ -295,12 +490,12 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
growCollectionIfNecessary(); |
|
|
|
growCollectionIfNecessary(); |
|
|
|
if (this.collection instanceof List) { |
|
|
|
if (this.collection instanceof List) { |
|
|
|
Object o = ((List) this.collection).get(this.index); |
|
|
|
Object o = ((List) this.collection).get(this.index); |
|
|
|
return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o)); |
|
|
|
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o)); |
|
|
|
} |
|
|
|
} |
|
|
|
int pos = 0; |
|
|
|
int pos = 0; |
|
|
|
for (Object o : this.collection) { |
|
|
|
for (Object o : this.collection) { |
|
|
|
if (pos == this.index) { |
|
|
|
if (pos == this.index) { |
|
|
|
return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o)); |
|
|
|
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o)); |
|
|
|
} |
|
|
|
} |
|
|
|
pos++; |
|
|
|
pos++; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -312,35 +507,31 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
growCollectionIfNecessary(); |
|
|
|
growCollectionIfNecessary(); |
|
|
|
if (this.collection instanceof List) { |
|
|
|
if (this.collection instanceof List) { |
|
|
|
List list = (List) this.collection; |
|
|
|
List list = (List) this.collection; |
|
|
|
if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) { |
|
|
|
if (this.collectionEntryDescriptor.getElementTypeDescriptor() != null) { |
|
|
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
this.collectionEntryTypeDescriptor.getElementTypeDescriptor()); |
|
|
|
this.collectionEntryDescriptor.getElementTypeDescriptor()); |
|
|
|
} |
|
|
|
} |
|
|
|
list.set(this.index, newValue); |
|
|
|
list.set(this.index, newValue); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, |
|
|
|
this.collectionEntryTypeDescriptor.toString()); |
|
|
|
this.collectionEntryDescriptor.toString()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void growCollectionIfNecessary() { |
|
|
|
private void growCollectionIfNecessary() { |
|
|
|
if (this.index >= this.collection.size()) { |
|
|
|
if (this.index >= this.collection.size()) { |
|
|
|
|
|
|
|
|
|
|
|
if (!this.growCollection) { |
|
|
|
if (!this.growCollection) { |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, |
|
|
|
this.collection.size(), this.index); |
|
|
|
this.collection.size(), this.index); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if(this.index >= this.maximumSize) { |
|
|
|
if(this.index >= this.maximumSize) { |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION); |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (this.collectionEntryDescriptor.getElementTypeDescriptor() == null) { |
|
|
|
if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() == null) { |
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); |
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
TypeDescriptor elementType = this.collectionEntryDescriptor.getElementTypeDescriptor(); |
|
|
|
TypeDescriptor elementType = this.collectionEntryTypeDescriptor.getElementTypeDescriptor(); |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
int newElements = this.index - this.collection.size(); |
|
|
|
int newElements = this.index - this.collection.size(); |
|
|
|
while (newElements >= 0) { |
|
|
|
while (newElements >= 0) { |
|
|
|
@ -369,14 +560,12 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor typeDescriptor; |
|
|
|
private final TypeDescriptor typeDescriptor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) { |
|
|
|
public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) { |
|
|
|
this.target = target; |
|
|
|
this.target = target; |
|
|
|
this.index = index; |
|
|
|
this.index = index; |
|
|
|
this.typeDescriptor = typeDescriptor; |
|
|
|
this.typeDescriptor = typeDescriptor; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public TypedValue getValue() { |
|
|
|
public TypedValue getValue() { |
|
|
|
if (this.index >= this.target.length()) { |
|
|
|
if (this.index >= this.target.length()) { |
|
|
|
@ -398,204 +587,4 @@ public class Indexer extends SpelNodeImpl { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TypedValue context = state.getActiveContextObject(); |
|
|
|
|
|
|
|
Object targetObject = context.getValue(); |
|
|
|
|
|
|
|
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor(); |
|
|
|
|
|
|
|
TypedValue indexValue = null; |
|
|
|
|
|
|
|
Object index = null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This first part of the if clause prevents a 'double dereference' of
|
|
|
|
|
|
|
|
// the property (SPR-5847)
|
|
|
|
|
|
|
|
if (targetObject instanceof Map && (this.children[0] instanceof PropertyOrFieldReference)) { |
|
|
|
|
|
|
|
PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0]; |
|
|
|
|
|
|
|
index = reference.getName(); |
|
|
|
|
|
|
|
indexValue = new TypedValue(index); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
// In case the map key is unqualified, we want it evaluated against
|
|
|
|
|
|
|
|
// the root object so temporarily push that on whilst evaluating the key
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
state.pushActiveContextObject(state.getRootContextObject()); |
|
|
|
|
|
|
|
indexValue = this.children[0].getValueInternal(state); |
|
|
|
|
|
|
|
index = indexValue.getValue(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
state.popActiveContextObject(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Indexing into a Map
|
|
|
|
|
|
|
|
if (targetObject instanceof Map) { |
|
|
|
|
|
|
|
Object key = index; |
|
|
|
|
|
|
|
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) { |
|
|
|
|
|
|
|
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return new MapIndexingValueRef(state.getTypeConverter(), (Map<?, ?>) targetObject, key, |
|
|
|
|
|
|
|
targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (targetObject == null) { |
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if the object is something that looks indexable by an integer,
|
|
|
|
|
|
|
|
// attempt to treat the index value as a number
|
|
|
|
|
|
|
|
if (targetObject.getClass().isArray() || targetObject instanceof Collection || targetObject instanceof String) { |
|
|
|
|
|
|
|
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); |
|
|
|
|
|
|
|
if (targetObject.getClass().isArray()) { |
|
|
|
|
|
|
|
return new ArrayIndexingValueRef(state.getTypeConverter(), targetObject, idx, targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (targetObject instanceof Collection) { |
|
|
|
|
|
|
|
return new CollectionIndexingValueRef((Collection<?>) targetObject, idx, targetObjectTypeDescriptor, |
|
|
|
|
|
|
|
state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(), |
|
|
|
|
|
|
|
state.getConfiguration().getMaximumAutoGrowSize()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (targetObject instanceof String) { |
|
|
|
|
|
|
|
return new StringIndexingLValue((String) targetObject, idx, targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Try and treat the index value as a property of the context object
|
|
|
|
|
|
|
|
// TODO could call the conversion service to convert the value to a String
|
|
|
|
|
|
|
|
if (indexValue.getTypeDescriptor().getType() == String.class) { |
|
|
|
|
|
|
|
return new PropertyIndexingValueRef(targetObject, (String) indexValue.getValue(), |
|
|
|
|
|
|
|
state.getEvaluationContext(), targetObjectTypeDescriptor); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, |
|
|
|
|
|
|
|
targetObjectTypeDescriptor.toString()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String toStringAST() { |
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
|
|
|
|
sb.append("["); |
|
|
|
|
|
|
|
for (int i = 0; i < getChildCount(); i++) { |
|
|
|
|
|
|
|
if (i > 0) { |
|
|
|
|
|
|
|
sb.append(","); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sb.append(getChild(i).toStringAST()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sb.append("]"); |
|
|
|
|
|
|
|
return sb.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void setArrayElement(TypeConverter converter, Object ctx, int idx, Object newValue, Class<?> clazz) |
|
|
|
|
|
|
|
throws EvaluationException { |
|
|
|
|
|
|
|
Class<?> arrayComponentType = clazz; |
|
|
|
|
|
|
|
if (arrayComponentType == Integer.TYPE) { |
|
|
|
|
|
|
|
int[] array = (int[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Integer) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Integer.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Boolean.TYPE) { |
|
|
|
|
|
|
|
boolean[] array = (boolean[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Boolean) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Boolean.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Character.TYPE) { |
|
|
|
|
|
|
|
char[] array = (char[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Character) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Character.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Long.TYPE) { |
|
|
|
|
|
|
|
long[] array = (long[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Long) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Long.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Short.TYPE) { |
|
|
|
|
|
|
|
short[] array = (short[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Short) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Short.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Double.TYPE) { |
|
|
|
|
|
|
|
double[] array = (double[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Double) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Double.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Float.TYPE) { |
|
|
|
|
|
|
|
float[] array = (float[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Float) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Float.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Byte.TYPE) { |
|
|
|
|
|
|
|
byte[] array = (byte[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = (Byte) converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(Byte.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
Object[] array = (Object[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
array[idx] = converter.convertValue(newValue, TypeDescriptor.forObject(newValue), |
|
|
|
|
|
|
|
TypeDescriptor.valueOf(clazz)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException { |
|
|
|
|
|
|
|
Class<?> arrayComponentType = ctx.getClass().getComponentType(); |
|
|
|
|
|
|
|
if (arrayComponentType == Integer.TYPE) { |
|
|
|
|
|
|
|
int[] array = (int[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Boolean.TYPE) { |
|
|
|
|
|
|
|
boolean[] array = (boolean[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Character.TYPE) { |
|
|
|
|
|
|
|
char[] array = (char[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Long.TYPE) { |
|
|
|
|
|
|
|
long[] array = (long[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Short.TYPE) { |
|
|
|
|
|
|
|
short[] array = (short[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Double.TYPE) { |
|
|
|
|
|
|
|
double[] array = (double[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Float.TYPE) { |
|
|
|
|
|
|
|
float[] array = (float[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else if (arrayComponentType == Byte.TYPE) { |
|
|
|
|
|
|
|
byte[] array = (byte[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
Object[] array = (Object[]) ctx; |
|
|
|
|
|
|
|
checkAccess(array.length, idx); |
|
|
|
|
|
|
|
return array[idx]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException { |
|
|
|
|
|
|
|
if (index > arrayLength) { |
|
|
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, |
|
|
|
|
|
|
|
arrayLength, index); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|