SpEL indexer uses direct access to specific List elements instead of iterating over the Collection
Issue: SPR-10035
This commit is contained in:
@@ -48,22 +48,30 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
// If the name and target type match these cached values then the cachedReadAccessor
|
// If the name and target type match these cached values then the cachedReadAccessor
|
||||||
// is used to read the property. If they do not match, the correct accessor is
|
// is used to read the property. If they do not match, the correct accessor is
|
||||||
// discovered and then cached for later use.
|
// discovered and then cached for later use.
|
||||||
|
|
||||||
private String cachedReadName;
|
private String cachedReadName;
|
||||||
|
|
||||||
private Class<?> cachedReadTargetType;
|
private Class<?> cachedReadTargetType;
|
||||||
|
|
||||||
private PropertyAccessor cachedReadAccessor;
|
private PropertyAccessor cachedReadAccessor;
|
||||||
|
|
||||||
// These fields are used when the indexer is being used as a property write accessor.
|
// These fields are used when the indexer is being used as a property write accessor.
|
||||||
// If the name and target type match these cached values then the cachedWriteAccessor
|
// If the name and target type match these cached values then the cachedWriteAccessor
|
||||||
// is used to write the property. If they do not match, the correct accessor is
|
// is used to write the property. If they do not match, the correct accessor is
|
||||||
// discovered and then cached for later use.
|
// discovered and then cached for later use.
|
||||||
|
|
||||||
private String cachedWriteName;
|
private String cachedWriteName;
|
||||||
|
|
||||||
private Class<?> cachedWriteTargetType;
|
private Class<?> cachedWriteTargetType;
|
||||||
|
|
||||||
private PropertyAccessor cachedWriteAccessor;
|
private PropertyAccessor cachedWriteAccessor;
|
||||||
|
|
||||||
|
|
||||||
public Indexer(int pos, SpelNodeImpl expr) {
|
public Indexer(int pos, SpelNodeImpl expr) {
|
||||||
super(pos, expr);
|
super(pos, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
return getValueRef(state).getValue();
|
return getValueRef(state).getValue();
|
||||||
@@ -75,20 +83,22 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWritable(ExpressionState expressionState)
|
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
|
||||||
throws SpelEvaluationException {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayIndexingValueRef implements ValueRef {
|
|
||||||
|
|
||||||
private TypeConverter typeConverter;
|
private class ArrayIndexingValueRef implements ValueRef {
|
||||||
private Object array;
|
|
||||||
private int idx;
|
|
||||||
private TypeDescriptor typeDescriptor;
|
|
||||||
|
|
||||||
ArrayIndexingValueRef(TypeConverter typeConverter, Object array,
|
private final TypeConverter typeConverter;
|
||||||
int idx, TypeDescriptor typeDescriptor) {
|
|
||||||
|
private final Object array;
|
||||||
|
|
||||||
|
private final int idx;
|
||||||
|
|
||||||
|
private final TypeDescriptor typeDescriptor;
|
||||||
|
|
||||||
|
ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int idx, TypeDescriptor typeDescriptor) {
|
||||||
this.typeConverter = typeConverter;
|
this.typeConverter = typeConverter;
|
||||||
this.array = array;
|
this.array = array;
|
||||||
this.idx = idx;
|
this.idx = idx;
|
||||||
@@ -96,14 +106,13 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TypedValue getValue() {
|
public TypedValue getValue() {
|
||||||
Object arrayElement = accessArrayElement(array, idx);
|
Object arrayElement = accessArrayElement(this.array, this.idx);
|
||||||
return new TypedValue(arrayElement,
|
return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement));
|
||||||
typeDescriptor.elementTypeDescriptor(arrayElement));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(Object newValue) {
|
public void setValue(Object newValue) {
|
||||||
setArrayElement(typeConverter, array, idx, newValue, typeDescriptor
|
setArrayElement(this.typeConverter, this.array, this.idx, newValue,
|
||||||
.getElementTypeDescriptor().getType());
|
this.typeDescriptor.getElementTypeDescriptor().getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWritable() {
|
public boolean isWritable() {
|
||||||
@@ -111,16 +120,19 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
||||||
class MapIndexingValueRef implements ValueRef {
|
|
||||||
|
|
||||||
private TypeConverter typeConverter;
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
private Map map;
|
private class MapIndexingValueRef implements ValueRef {
|
||||||
private Object key;
|
|
||||||
private TypeDescriptor mapEntryTypeDescriptor;
|
|
||||||
|
|
||||||
MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key,
|
private final TypeConverter typeConverter;
|
||||||
TypeDescriptor mapEntryTypeDescriptor) {
|
|
||||||
|
private final Map map;
|
||||||
|
|
||||||
|
private final Object key;
|
||||||
|
|
||||||
|
private final 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;
|
||||||
@@ -128,35 +140,35 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TypedValue getValue() {
|
public TypedValue getValue() {
|
||||||
Object value = map.get(key);
|
Object value = this.map.get(this.key);
|
||||||
return new TypedValue(value,
|
return new TypedValue(value, this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value));
|
||||||
mapEntryTypeDescriptor.getMapValueTypeDescriptor(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(Object newValue) {
|
public void setValue(Object newValue) {
|
||||||
if (mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) {
|
if (this.mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) {
|
||||||
newValue = typeConverter.convertValue(newValue,
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
this.mapEntryTypeDescriptor.getMapValueTypeDescriptor());
|
||||||
mapEntryTypeDescriptor.getMapValueTypeDescriptor());
|
|
||||||
}
|
}
|
||||||
map.put(key, newValue);
|
this.map.put(this.key, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWritable() {
|
public boolean isWritable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyIndexingValueRef implements ValueRef {
|
|
||||||
|
|
||||||
private Object targetObject;
|
private class PropertyIndexingValueRef implements ValueRef {
|
||||||
private String name;
|
|
||||||
private EvaluationContext eContext;
|
|
||||||
private TypeDescriptor td;
|
|
||||||
|
|
||||||
public PropertyIndexingValueRef(Object targetObject, String value,
|
private final Object targetObject;
|
||||||
EvaluationContext evaluationContext,
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final EvaluationContext eContext;
|
||||||
|
|
||||||
|
private final TypeDescriptor td;
|
||||||
|
|
||||||
|
public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext,
|
||||||
TypeDescriptor targetObjectTypeDescriptor) {
|
TypeDescriptor targetObjectTypeDescriptor) {
|
||||||
this.targetObject = targetObject;
|
this.targetObject = targetObject;
|
||||||
this.name = value;
|
this.name = value;
|
||||||
@@ -166,78 +178,63 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
|
|
||||||
public TypedValue getValue() {
|
public TypedValue getValue() {
|
||||||
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
|
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cachedReadName != null
|
if (cachedReadName != null && cachedReadName.equals(name) && cachedReadTargetType != null &&
|
||||||
&& cachedReadName.equals(name)
|
cachedReadTargetType.equals(targetObjectRuntimeClass)) {
|
||||||
&& cachedReadTargetType != null
|
|
||||||
&& cachedReadTargetType
|
|
||||||
.equals(targetObjectRuntimeClass)) {
|
|
||||||
// it is OK to use the cached accessor
|
// it is OK to use the cached accessor
|
||||||
return cachedReadAccessor
|
return cachedReadAccessor.read(this.eContext, this.targetObject, this.name);
|
||||||
.read(eContext, targetObject, name);
|
|
||||||
}
|
}
|
||||||
|
List<PropertyAccessor> accessorsToTry =
|
||||||
List<PropertyAccessor> accessorsToTry = AstUtils
|
AstUtils.getPropertyAccessorsToTry(targetObjectRuntimeClass, eContext.getPropertyAccessors());
|
||||||
.getPropertyAccessorsToTry(targetObjectRuntimeClass,
|
|
||||||
eContext.getPropertyAccessors());
|
|
||||||
|
|
||||||
if (accessorsToTry != null) {
|
if (accessorsToTry != null) {
|
||||||
for (PropertyAccessor accessor : accessorsToTry) {
|
for (PropertyAccessor accessor : accessorsToTry) {
|
||||||
if (accessor.canRead(eContext, targetObject, name)) {
|
if (accessor.canRead(this.eContext, this.targetObject, this.name)) {
|
||||||
if (accessor instanceof ReflectivePropertyAccessor) {
|
if (accessor instanceof ReflectivePropertyAccessor) {
|
||||||
accessor = ((ReflectivePropertyAccessor) accessor)
|
accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
|
||||||
.createOptimalAccessor(eContext,
|
this.eContext, this.targetObject, this.name);
|
||||||
targetObject, name);
|
|
||||||
}
|
}
|
||||||
cachedReadAccessor = accessor;
|
cachedReadAccessor = accessor;
|
||||||
cachedReadName = name;
|
cachedReadName = this.name;
|
||||||
cachedReadTargetType = targetObjectRuntimeClass;
|
cachedReadTargetType = targetObjectRuntimeClass;
|
||||||
return accessor.read(eContext, targetObject, name);
|
return accessor.read(this.eContext, this.targetObject, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AccessException e) {
|
|
||||||
throw new SpelEvaluationException(getStartPosition(), e,
|
|
||||||
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
||||||
td.toString());
|
|
||||||
}
|
}
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
catch (AccessException ex) {
|
||||||
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, td.toString());
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
||||||
|
this.td.toString());
|
||||||
|
}
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
||||||
|
this.td.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(Object newValue) {
|
public void setValue(Object newValue) {
|
||||||
Class<?> contextObjectClass = getObjectClass(targetObject);
|
Class<?> contextObjectClass = getObjectClass(targetObject);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cachedWriteName != null && cachedWriteName.equals(name)
|
if (cachedWriteName != null && cachedWriteName.equals(name) && cachedWriteTargetType != null &&
|
||||||
&& cachedWriteTargetType != null
|
cachedWriteTargetType.equals(contextObjectClass)) {
|
||||||
&& cachedWriteTargetType.equals(contextObjectClass)) {
|
|
||||||
// it is OK to use the cached accessor
|
// it is OK to use the cached accessor
|
||||||
cachedWriteAccessor.write(eContext, targetObject, name,
|
cachedWriteAccessor.write(this.eContext, this.targetObject, this.name, newValue);
|
||||||
newValue);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
List<PropertyAccessor> accessorsToTry =
|
||||||
List<PropertyAccessor> accessorsToTry = AstUtils
|
AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.eContext.getPropertyAccessors());
|
||||||
.getPropertyAccessorsToTry(contextObjectClass,
|
|
||||||
eContext.getPropertyAccessors());
|
|
||||||
if (accessorsToTry != null) {
|
if (accessorsToTry != null) {
|
||||||
for (PropertyAccessor accessor : accessorsToTry) {
|
for (PropertyAccessor accessor : accessorsToTry) {
|
||||||
if (accessor.canWrite(eContext, targetObject, name)) {
|
if (accessor.canWrite(this.eContext, this.targetObject, this.name)) {
|
||||||
cachedWriteName = name;
|
cachedWriteName = this.name;
|
||||||
cachedWriteTargetType = contextObjectClass;
|
cachedWriteTargetType = contextObjectClass;
|
||||||
cachedWriteAccessor = accessor;
|
cachedWriteAccessor = accessor;
|
||||||
accessor.write(eContext, targetObject, name,
|
accessor.write(this.eContext, this.targetObject, this.name, newValue);
|
||||||
newValue);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AccessException ae) {
|
}
|
||||||
throw new SpelEvaluationException(getStartPosition(), ae,
|
catch (AccessException ex) {
|
||||||
SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE, name,
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
|
||||||
ae.getMessage());
|
this.name, ex.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,70 +243,74 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
class CollectionIndexingValueRef implements ValueRef {
|
private class CollectionIndexingValueRef implements ValueRef {
|
||||||
|
|
||||||
private TypeConverter typeConverter;
|
private final Collection collection;
|
||||||
private Collection collection;
|
|
||||||
private int index;
|
|
||||||
private TypeDescriptor collectionEntryTypeDescriptor;
|
|
||||||
private boolean growCollection;
|
|
||||||
|
|
||||||
CollectionIndexingValueRef(Collection collection, int index,
|
private final int index;
|
||||||
TypeDescriptor collectionEntryTypeDescriptor, TypeConverter typeConverter, boolean growCollection) {
|
|
||||||
this.typeConverter = typeConverter;
|
private final TypeDescriptor collectionEntryTypeDescriptor;
|
||||||
this.growCollection = growCollection;
|
|
||||||
|
private final TypeConverter typeConverter;
|
||||||
|
|
||||||
|
private final boolean growCollection;
|
||||||
|
|
||||||
|
CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor,
|
||||||
|
TypeConverter typeConverter, boolean growCollection) {
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor;
|
this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor;
|
||||||
|
this.typeConverter = typeConverter;
|
||||||
|
this.growCollection = growCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypedValue getValue() {
|
public TypedValue getValue() {
|
||||||
if (index >= collection.size()) {
|
if (this.index >= this.collection.size()) {
|
||||||
if (growCollection) {
|
if (this.growCollection) {
|
||||||
growCollection(collectionEntryTypeDescriptor,index,collection);
|
growCollection(this.collectionEntryTypeDescriptor, this.index, this.collection);
|
||||||
} else {
|
}
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
else {
|
||||||
SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
|
||||||
collection.size(), index);
|
this.collection.size(), this.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.collection instanceof List) {
|
||||||
|
Object o = ((List) this.collection).get(this.index);
|
||||||
|
return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o));
|
||||||
|
}
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
for (Object o : collection) {
|
for (Object o : this.collection) {
|
||||||
if (pos == index) {
|
if (pos == this.index) {
|
||||||
return new TypedValue(o,
|
return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o));
|
||||||
collectionEntryTypeDescriptor
|
|
||||||
.elementTypeDescriptor(o));
|
|
||||||
}
|
}
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException("Failed to find indexed element " + this.index + ": " + this.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(Object newValue) {
|
public void setValue(Object newValue) {
|
||||||
if (index >= collection.size()) {
|
if (this.index >= this.collection.size()) {
|
||||||
if (growCollection) {
|
if (this.growCollection) {
|
||||||
growCollection(collectionEntryTypeDescriptor, index, collection);
|
growCollection(this.collectionEntryTypeDescriptor, this.index, this.collection);
|
||||||
} else {
|
}
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
else {
|
||||||
SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
|
||||||
collection.size(), index);
|
this.collection.size(), this.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (collection instanceof List) {
|
if (this.collection instanceof List) {
|
||||||
List list = (List) collection;
|
List list = (List) this.collection;
|
||||||
if (collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) {
|
if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) {
|
||||||
newValue = typeConverter.convertValue(newValue,
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
this.collectionEntryTypeDescriptor.getElementTypeDescriptor());
|
||||||
collectionEntryTypeDescriptor
|
|
||||||
.getElementTypeDescriptor());
|
|
||||||
}
|
}
|
||||||
list.set(index, newValue);
|
list.set(this.index, newValue);
|
||||||
return;
|
}
|
||||||
} else {
|
else {
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
||||||
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
this.collectionEntryTypeDescriptor.toString());
|
||||||
collectionEntryTypeDescriptor.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,11 +319,14 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringIndexingLValue implements ValueRef {
|
|
||||||
|
|
||||||
private String target;
|
private class StringIndexingLValue implements ValueRef {
|
||||||
private int index;
|
|
||||||
private TypeDescriptor td;
|
private final String target;
|
||||||
|
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
private final TypeDescriptor td;
|
||||||
|
|
||||||
public StringIndexingLValue(String target, int index, TypeDescriptor td) {
|
public StringIndexingLValue(String target, int index, TypeDescriptor td) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
@@ -331,28 +335,25 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TypedValue getValue() {
|
public TypedValue getValue() {
|
||||||
if (index >= target.length()) {
|
if (this.index >= this.target.length()) {
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
|
||||||
SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
|
this.target.length(), index);
|
||||||
target.length(), index);
|
|
||||||
}
|
}
|
||||||
return new TypedValue(String.valueOf(target.charAt(index)));
|
return new TypedValue(String.valueOf(this.target.charAt(this.index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(Object newValue) {
|
public void setValue(Object newValue) {
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
||||||
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, td.toString());
|
this.td.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWritable() {
|
public boolean isWritable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ValueRef getValueRef(ExpressionState state)
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||||
throws EvaluationException {
|
|
||||||
TypedValue context = state.getActiveContextObject();
|
TypedValue context = state.getActiveContextObject();
|
||||||
Object targetObject = context.getValue();
|
Object targetObject = context.getValue();
|
||||||
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor();
|
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor();
|
||||||
@@ -361,18 +362,20 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
|
|
||||||
// This first part of the if clause prevents a 'double dereference' of
|
// This first part of the if clause prevents a 'double dereference' of
|
||||||
// the property (SPR-5847)
|
// the property (SPR-5847)
|
||||||
if (targetObject instanceof Map && (children[0] instanceof PropertyOrFieldReference)) {
|
if (targetObject instanceof Map && (this.children[0] instanceof PropertyOrFieldReference)) {
|
||||||
PropertyOrFieldReference reference = (PropertyOrFieldReference) children[0];
|
PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0];
|
||||||
index = reference.getName();
|
index = reference.getName();
|
||||||
indexValue = new TypedValue(index);
|
indexValue = new TypedValue(index);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// In case the map key is unqualified, we want it evaluated against
|
// In case the map key is unqualified, we want it evaluated against
|
||||||
// the root object so temporarily push that on whilst evaluating the key
|
// the root object so temporarily push that on whilst evaluating the key
|
||||||
try {
|
try {
|
||||||
state.pushActiveContextObject(state.getRootContextObject());
|
state.pushActiveContextObject(state.getRootContextObject());
|
||||||
indexValue = children[0].getValueInternal(state);
|
indexValue = this.children[0].getValueInternal(state);
|
||||||
index = indexValue.getValue();
|
index = indexValue.getValue();
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
state.popActiveContextObject();
|
state.popActiveContextObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,69 +384,55 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
if (targetObject instanceof Map) {
|
if (targetObject instanceof Map) {
|
||||||
Object key = index;
|
Object key = index;
|
||||||
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
|
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
|
||||||
key = state.convertValue(key,
|
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
||||||
targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
|
||||||
}
|
}
|
||||||
return new MapIndexingValueRef(state.getTypeConverter(),
|
return new MapIndexingValueRef(state.getTypeConverter(), (Map<?, ?>) targetObject, key,
|
||||||
(Map<?, ?>) targetObject, key, targetObjectTypeDescriptor);
|
targetObjectTypeDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetObject == null) {
|
if (targetObject == null) {
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
|
||||||
SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the object is something that looks indexable by an integer,
|
// if the object is something that looks indexable by an integer,
|
||||||
// attempt to treat the index value as a number
|
// attempt to treat the index value as a number
|
||||||
if (targetObject instanceof Collection
|
if (targetObject.getClass().isArray() || targetObject instanceof Collection || targetObject instanceof String) {
|
||||||
|| targetObject.getClass().isArray()
|
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
||||||
|| targetObject instanceof String) {
|
|
||||||
int idx = (Integer) state.convertValue(index,
|
|
||||||
TypeDescriptor.valueOf(Integer.class));
|
|
||||||
if (targetObject.getClass().isArray()) {
|
if (targetObject.getClass().isArray()) {
|
||||||
return new ArrayIndexingValueRef(state.getTypeConverter(),
|
return new ArrayIndexingValueRef(state.getTypeConverter(), targetObject, idx, targetObjectTypeDescriptor);
|
||||||
targetObject, idx, targetObjectTypeDescriptor);
|
}
|
||||||
} else if (targetObject instanceof Collection) {
|
else if (targetObject instanceof Collection) {
|
||||||
return new CollectionIndexingValueRef(
|
return new CollectionIndexingValueRef((Collection<?>) targetObject, idx, targetObjectTypeDescriptor,
|
||||||
(Collection<?>) targetObject, idx,
|
state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections());
|
||||||
targetObjectTypeDescriptor,state.getTypeConverter(),
|
}
|
||||||
state.getConfiguration().isAutoGrowCollections());
|
else if (targetObject instanceof String) {
|
||||||
} else if (targetObject instanceof String) {
|
return new StringIndexingLValue((String) targetObject, idx, targetObjectTypeDescriptor);
|
||||||
return new StringIndexingLValue((String) targetObject, idx,
|
|
||||||
targetObjectTypeDescriptor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and treat the index value as a property of the context object
|
// 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
|
// TODO could call the conversion service to convert the value to a String
|
||||||
// String
|
|
||||||
if (indexValue.getTypeDescriptor().getType() == String.class) {
|
if (indexValue.getTypeDescriptor().getType() == String.class) {
|
||||||
return new PropertyIndexingValueRef(targetObject,
|
return new PropertyIndexingValueRef(targetObject, (String) indexValue.getValue(),
|
||||||
(String) indexValue.getValue(),
|
|
||||||
state.getEvaluationContext(), targetObjectTypeDescriptor);
|
state.getEvaluationContext(), targetObjectTypeDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
||||||
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
||||||
targetObjectTypeDescriptor.toString());
|
targetObjectTypeDescriptor.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to grow the specified collection so that the specified index is
|
* Attempt to grow the specified collection so that the specified index is valid.
|
||||||
* valid.
|
* @param targetType the type of the elements in the collection
|
||||||
*
|
|
||||||
* @param elementType the type of the elements in the collection
|
|
||||||
* @param index the index into the collection that needs to be valid
|
* @param index the index into the collection that needs to be valid
|
||||||
* @param collection the collection to grow with elements
|
* @param collection the collection to grow with elements
|
||||||
*/
|
*/
|
||||||
private void growCollection(TypeDescriptor targetType, int index, Collection<Object> collection) {
|
private void growCollection(TypeDescriptor targetType, int index, Collection<Object> collection) {
|
||||||
if (targetType.getElementTypeDescriptor() == null) {
|
if (targetType.getElementTypeDescriptor() == null) {
|
||||||
throw new SpelEvaluationException(
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
||||||
getStartPosition(),
|
|
||||||
SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
|
||||||
}
|
}
|
||||||
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
|
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
|
||||||
Object newCollectionElement = null;
|
Object newCollectionElement;
|
||||||
try {
|
try {
|
||||||
int newElements = index - collection.size();
|
int newElements = index - collection.size();
|
||||||
while (newElements > 0) {
|
while (newElements > 0) {
|
||||||
@@ -451,9 +440,9 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
newElements--;
|
newElements--;
|
||||||
}
|
}
|
||||||
newCollectionElement = elementType.getType().newInstance();
|
newCollectionElement = elementType.getType().newInstance();
|
||||||
} catch (Exception ex) {
|
}
|
||||||
throw new SpelEvaluationException(getStartPosition(), ex,
|
catch (Exception ex) {
|
||||||
SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
||||||
}
|
}
|
||||||
collection.add(newCollectionElement);
|
collection.add(newCollectionElement);
|
||||||
}
|
}
|
||||||
@@ -463,70 +452,70 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("[");
|
sb.append("[");
|
||||||
for (int i = 0; i < getChildCount(); i++) {
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
if (i > 0)
|
if (i > 0) {
|
||||||
sb.append(",");
|
sb.append(",");
|
||||||
|
}
|
||||||
sb.append(getChild(i).toStringAST());
|
sb.append(getChild(i).toStringAST());
|
||||||
}
|
}
|
||||||
sb.append("]");
|
sb.append("]");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setArrayElement(TypeConverter converter, Object ctx, int idx,
|
private void setArrayElement(TypeConverter converter, Object ctx, int idx, Object newValue, Class<?> clazz)
|
||||||
Object newValue, Class<?> clazz) throws EvaluationException {
|
throws EvaluationException {
|
||||||
Class<?> arrayComponentType = clazz;
|
Class<?> arrayComponentType = clazz;
|
||||||
if (arrayComponentType == Integer.TYPE) {
|
if (arrayComponentType == Integer.TYPE) {
|
||||||
int[] array = (int[]) ctx;
|
int[] array = (int[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Integer) converter.convertValue(newValue,
|
array[idx] = (Integer) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Integer.class));
|
TypeDescriptor.valueOf(Integer.class));
|
||||||
} else if (arrayComponentType == Boolean.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Boolean.TYPE) {
|
||||||
boolean[] array = (boolean[]) ctx;
|
boolean[] array = (boolean[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Boolean) converter.convertValue(newValue,
|
array[idx] = (Boolean) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Boolean.class));
|
TypeDescriptor.valueOf(Boolean.class));
|
||||||
} else if (arrayComponentType == Character.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Character.TYPE) {
|
||||||
char[] array = (char[]) ctx;
|
char[] array = (char[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Character) converter.convertValue(newValue,
|
array[idx] = (Character) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Character.class));
|
TypeDescriptor.valueOf(Character.class));
|
||||||
} else if (arrayComponentType == Long.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Long.TYPE) {
|
||||||
long[] array = (long[]) ctx;
|
long[] array = (long[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Long) converter.convertValue(newValue,
|
array[idx] = (Long) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Long.class));
|
TypeDescriptor.valueOf(Long.class));
|
||||||
} else if (arrayComponentType == Short.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Short.TYPE) {
|
||||||
short[] array = (short[]) ctx;
|
short[] array = (short[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Short) converter.convertValue(newValue,
|
array[idx] = (Short) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Short.class));
|
TypeDescriptor.valueOf(Short.class));
|
||||||
} else if (arrayComponentType == Double.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Double.TYPE) {
|
||||||
double[] array = (double[]) ctx;
|
double[] array = (double[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Double) converter.convertValue(newValue,
|
array[idx] = (Double) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Double.class));
|
TypeDescriptor.valueOf(Double.class));
|
||||||
} else if (arrayComponentType == Float.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Float.TYPE) {
|
||||||
float[] array = (float[]) ctx;
|
float[] array = (float[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Float) converter.convertValue(newValue,
|
array[idx] = (Float) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Float.class));
|
TypeDescriptor.valueOf(Float.class));
|
||||||
} else if (arrayComponentType == Byte.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Byte.TYPE) {
|
||||||
byte[] array = (byte[]) ctx;
|
byte[] array = (byte[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Byte) converter.convertValue(newValue,
|
array[idx] = (Byte) converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(Byte.class));
|
TypeDescriptor.valueOf(Byte.class));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Object[] array = (Object[]) ctx;
|
Object[] array = (Object[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = converter.convertValue(newValue,
|
array[idx] = converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
||||||
TypeDescriptor.forObject(newValue),
|
|
||||||
TypeDescriptor.valueOf(clazz));
|
TypeDescriptor.valueOf(clazz));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,35 +526,43 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
int[] array = (int[]) ctx;
|
int[] array = (int[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Boolean.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Boolean.TYPE) {
|
||||||
boolean[] array = (boolean[]) ctx;
|
boolean[] array = (boolean[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Character.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Character.TYPE) {
|
||||||
char[] array = (char[]) ctx;
|
char[] array = (char[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Long.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Long.TYPE) {
|
||||||
long[] array = (long[]) ctx;
|
long[] array = (long[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Short.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Short.TYPE) {
|
||||||
short[] array = (short[]) ctx;
|
short[] array = (short[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Double.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Double.TYPE) {
|
||||||
double[] array = (double[]) ctx;
|
double[] array = (double[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Float.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Float.TYPE) {
|
||||||
float[] array = (float[]) ctx;
|
float[] array = (float[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else if (arrayComponentType == Byte.TYPE) {
|
}
|
||||||
|
else if (arrayComponentType == Byte.TYPE) {
|
||||||
byte[] array = (byte[]) ctx;
|
byte[] array = (byte[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Object[] array = (Object[]) ctx;
|
Object[] array = (Object[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
return array[idx];
|
return array[idx];
|
||||||
@@ -574,9 +571,9 @@ public class Indexer extends SpelNodeImpl {
|
|||||||
|
|
||||||
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
|
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
|
||||||
if (index > arrayLength) {
|
if (index > arrayLength) {
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS,
|
||||||
SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index);
|
arrayLength, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user