|
|
|
|
@@ -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");
|
|
|
|
|
* 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.
|
|
|
|
|
* Supported structures are: strings/collections (lists/sets)/arrays
|
|
|
|
|
* Supported structures are: strings / collections (lists/sets) / arrays.
|
|
|
|
|
*
|
|
|
|
|
* @author Andy Clement
|
|
|
|
|
* @author Phillip Webb
|
|
|
|
|
@@ -88,319 +88,8 @@ public class Indexer extends SpelNodeImpl {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class ArrayIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter;
|
|
|
|
|
|
|
|
|
|
private final Object array;
|
|
|
|
|
|
|
|
|
|
private final int index;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor typeDescriptor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) {
|
|
|
|
|
this.typeConverter = typeConverter;
|
|
|
|
|
this.array = array;
|
|
|
|
|
this.index = index;
|
|
|
|
|
this.typeDescriptor = typeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
Object arrayElement = accessArrayElement(this.array, this.index);
|
|
|
|
|
return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
setArrayElement(this.typeConverter, this.array, this.index, newValue,
|
|
|
|
|
this.typeDescriptor.getElementTypeDescriptor().getType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
|
|
|
private class MapIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter;
|
|
|
|
|
|
|
|
|
|
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.map = map;
|
|
|
|
|
this.key = key;
|
|
|
|
|
this.mapEntryTypeDescriptor = mapEntryTypeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
Object value = this.map.get(this.key);
|
|
|
|
|
return new TypedValue(value,
|
|
|
|
|
this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
if (this.mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) {
|
|
|
|
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
|
|
|
|
this.mapEntryTypeDescriptor.getMapValueTypeDescriptor());
|
|
|
|
|
}
|
|
|
|
|
this.map.put(this.key, newValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class PropertyIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final Object targetObject;
|
|
|
|
|
|
|
|
|
|
private final String name;
|
|
|
|
|
|
|
|
|
|
private final EvaluationContext evaluationContext;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor targetObjectTypeDescriptor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext,
|
|
|
|
|
TypeDescriptor targetObjectTypeDescriptor) {
|
|
|
|
|
this.targetObject = targetObject;
|
|
|
|
|
this.name = value;
|
|
|
|
|
this.evaluationContext = evaluationContext;
|
|
|
|
|
this.targetObjectTypeDescriptor = targetObjectTypeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
Class<?> targetObjectRuntimeClass = getObjectClass(this.targetObject);
|
|
|
|
|
try {
|
|
|
|
|
if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) && Indexer.this.cachedReadTargetType != null &&
|
|
|
|
|
Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) {
|
|
|
|
|
// it is OK to use the cached accessor
|
|
|
|
|
return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(
|
|
|
|
|
targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors());
|
|
|
|
|
|
|
|
|
|
if (accessorsToTry != null) {
|
|
|
|
|
for (PropertyAccessor accessor : accessorsToTry) {
|
|
|
|
|
if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) {
|
|
|
|
|
if (accessor instanceof ReflectivePropertyAccessor) {
|
|
|
|
|
accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
|
|
|
|
|
this.evaluationContext, this.targetObject, this.name);
|
|
|
|
|
}
|
|
|
|
|
Indexer.this.cachedReadAccessor = accessor;
|
|
|
|
|
Indexer.this.cachedReadName = this.name;
|
|
|
|
|
Indexer.this.cachedReadTargetType = targetObjectRuntimeClass;
|
|
|
|
|
return accessor.read(this.evaluationContext, this.targetObject, this.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (AccessException ex) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.targetObjectTypeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.targetObjectTypeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
Class<?> contextObjectClass = getObjectClass(this.targetObject);
|
|
|
|
|
try {
|
|
|
|
|
if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) && Indexer.this.cachedWriteTargetType != null &&
|
|
|
|
|
Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) {
|
|
|
|
|
// it is OK to use the cached accessor
|
|
|
|
|
Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
List<PropertyAccessor> accessorsToTry =
|
|
|
|
|
AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.evaluationContext.getPropertyAccessors());
|
|
|
|
|
if (accessorsToTry != null) {
|
|
|
|
|
for (PropertyAccessor accessor : accessorsToTry) {
|
|
|
|
|
if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) {
|
|
|
|
|
Indexer.this.cachedWriteName = this.name;
|
|
|
|
|
Indexer.this.cachedWriteTargetType = contextObjectClass;
|
|
|
|
|
Indexer.this.cachedWriteAccessor = accessor;
|
|
|
|
|
accessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (AccessException ex) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
|
|
|
|
|
this.name, ex.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
|
|
|
private class CollectionIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final Collection collection;
|
|
|
|
|
|
|
|
|
|
private final int index;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor collectionEntryTypeDescriptor;
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter;
|
|
|
|
|
|
|
|
|
|
private final boolean growCollection;
|
|
|
|
|
|
|
|
|
|
private final int maximumSize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor,
|
|
|
|
|
TypeConverter typeConverter, boolean growCollection, int maximumSize) {
|
|
|
|
|
this.collection = collection;
|
|
|
|
|
this.index = index;
|
|
|
|
|
this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor;
|
|
|
|
|
this.typeConverter = typeConverter;
|
|
|
|
|
this.growCollection = growCollection;
|
|
|
|
|
this.maximumSize = maximumSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
growCollectionIfNecessary();
|
|
|
|
|
if (this.collection instanceof List) {
|
|
|
|
|
Object o = ((List) this.collection).get(this.index);
|
|
|
|
|
return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o));
|
|
|
|
|
}
|
|
|
|
|
int pos = 0;
|
|
|
|
|
for (Object o : this.collection) {
|
|
|
|
|
if (pos == this.index) {
|
|
|
|
|
return new TypedValue(o, this.collectionEntryTypeDescriptor.elementTypeDescriptor(o));
|
|
|
|
|
}
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
throw new IllegalStateException("Failed to find indexed element " + this.index + ": " + this.collection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
growCollectionIfNecessary();
|
|
|
|
|
if (this.collection instanceof List) {
|
|
|
|
|
List list = (List) this.collection;
|
|
|
|
|
if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) {
|
|
|
|
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
|
|
|
|
this.collectionEntryTypeDescriptor.getElementTypeDescriptor());
|
|
|
|
|
}
|
|
|
|
|
list.set(this.index, newValue);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.collectionEntryTypeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void growCollectionIfNecessary() {
|
|
|
|
|
if (this.index >= this.collection.size()) {
|
|
|
|
|
|
|
|
|
|
if (!this.growCollection) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
|
|
|
|
|
this.collection.size(), this.index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(this.index >= this.maximumSize) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.collectionEntryTypeDescriptor.getElementTypeDescriptor() == null) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TypeDescriptor elementType = this.collectionEntryTypeDescriptor.getElementTypeDescriptor();
|
|
|
|
|
try {
|
|
|
|
|
int newElements = this.index - this.collection.size();
|
|
|
|
|
while (newElements >= 0) {
|
|
|
|
|
(this.collection).add(elementType.getType().newInstance());
|
|
|
|
|
newElements--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class StringIndexingLValue implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final String target;
|
|
|
|
|
|
|
|
|
|
private final int index;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor typeDescriptor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) {
|
|
|
|
|
this.target = target;
|
|
|
|
|
this.index = index;
|
|
|
|
|
this.typeDescriptor = typeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
if (this.index >= this.target.length()) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
|
|
|
|
|
this.target.length(), this.index);
|
|
|
|
|
}
|
|
|
|
|
return new TypedValue(String.valueOf(this.target.charAt(this.index)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.typeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
|
|
|
|
|
|
|
|
|
TypedValue context = state.getActiveContextObject();
|
|
|
|
|
Object targetObject = context.getValue();
|
|
|
|
|
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor();
|
|
|
|
|
@@ -483,9 +172,9 @@ public class Indexer extends SpelNodeImpl {
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void setArrayElement(TypeConverter converter, Object ctx, int idx, Object newValue, Class<?> clazz)
|
|
|
|
|
throws EvaluationException {
|
|
|
|
|
Class<?> arrayComponentType = clazz;
|
|
|
|
|
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);
|
|
|
|
|
@@ -538,7 +227,7 @@ public class Indexer extends SpelNodeImpl {
|
|
|
|
|
Object[] array = (Object[]) ctx;
|
|
|
|
|
checkAccess(array.length, idx);
|
|
|
|
|
array[idx] = converter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
|
|
|
|
TypeDescriptor.valueOf(clazz));
|
|
|
|
|
TypeDescriptor.valueOf(arrayComponentType));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -598,4 +287,304 @@ public class Indexer extends SpelNodeImpl {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class ArrayIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter;
|
|
|
|
|
|
|
|
|
|
private final Object array;
|
|
|
|
|
|
|
|
|
|
private final int index;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor typeDescriptor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ArrayIndexingValueRef(TypeConverter typeConverter, Object array, int index, TypeDescriptor typeDescriptor) {
|
|
|
|
|
this.typeConverter = typeConverter;
|
|
|
|
|
this.array = array;
|
|
|
|
|
this.index = index;
|
|
|
|
|
this.typeDescriptor = typeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
Object arrayElement = accessArrayElement(this.array, this.index);
|
|
|
|
|
return new TypedValue(arrayElement, this.typeDescriptor.elementTypeDescriptor(arrayElement));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
setArrayElement(this.typeConverter, this.array, this.index, newValue,
|
|
|
|
|
this.typeDescriptor.getElementTypeDescriptor().getType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
|
|
|
private static class MapIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter;
|
|
|
|
|
|
|
|
|
|
private final Map map;
|
|
|
|
|
|
|
|
|
|
private final Object key;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor mapEntryTypeDescriptor;
|
|
|
|
|
|
|
|
|
|
public MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, TypeDescriptor mapEntryTypeDescriptor) {
|
|
|
|
|
this.typeConverter = typeConverter;
|
|
|
|
|
this.map = map;
|
|
|
|
|
this.key = key;
|
|
|
|
|
this.mapEntryTypeDescriptor = mapEntryTypeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
Object value = this.map.get(this.key);
|
|
|
|
|
return new TypedValue(value,
|
|
|
|
|
this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
if (this.mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) {
|
|
|
|
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
|
|
|
|
this.mapEntryTypeDescriptor.getMapValueTypeDescriptor());
|
|
|
|
|
}
|
|
|
|
|
this.map.put(this.key, newValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class PropertyIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final Object targetObject;
|
|
|
|
|
|
|
|
|
|
private final String name;
|
|
|
|
|
|
|
|
|
|
private final EvaluationContext evaluationContext;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor targetObjectTypeDescriptor;
|
|
|
|
|
|
|
|
|
|
public PropertyIndexingValueRef(Object targetObject, String value, EvaluationContext evaluationContext,
|
|
|
|
|
TypeDescriptor targetObjectTypeDescriptor) {
|
|
|
|
|
this.targetObject = targetObject;
|
|
|
|
|
this.name = value;
|
|
|
|
|
this.evaluationContext = evaluationContext;
|
|
|
|
|
this.targetObjectTypeDescriptor = targetObjectTypeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
Class<?> targetObjectRuntimeClass = getObjectClass(this.targetObject);
|
|
|
|
|
try {
|
|
|
|
|
if (Indexer.this.cachedReadName != null && Indexer.this.cachedReadName.equals(this.name) &&
|
|
|
|
|
Indexer.this.cachedReadTargetType != null &&
|
|
|
|
|
Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) {
|
|
|
|
|
// It is OK to use the cached accessor
|
|
|
|
|
return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name);
|
|
|
|
|
}
|
|
|
|
|
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(
|
|
|
|
|
targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors());
|
|
|
|
|
if (accessorsToTry != null) {
|
|
|
|
|
for (PropertyAccessor accessor : accessorsToTry) {
|
|
|
|
|
if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) {
|
|
|
|
|
if (accessor instanceof ReflectivePropertyAccessor) {
|
|
|
|
|
accessor = ((ReflectivePropertyAccessor) accessor).createOptimalAccessor(
|
|
|
|
|
this.evaluationContext, this.targetObject, this.name);
|
|
|
|
|
}
|
|
|
|
|
Indexer.this.cachedReadAccessor = accessor;
|
|
|
|
|
Indexer.this.cachedReadName = this.name;
|
|
|
|
|
Indexer.this.cachedReadTargetType = targetObjectRuntimeClass;
|
|
|
|
|
return accessor.read(this.evaluationContext, this.targetObject, this.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (AccessException ex) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.targetObjectTypeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.targetObjectTypeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
Class<?> contextObjectClass = getObjectClass(this.targetObject);
|
|
|
|
|
try {
|
|
|
|
|
if (Indexer.this.cachedWriteName != null && Indexer.this.cachedWriteName.equals(this.name) &&
|
|
|
|
|
Indexer.this.cachedWriteTargetType != null &&
|
|
|
|
|
Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) {
|
|
|
|
|
// It is OK to use the cached accessor
|
|
|
|
|
Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
List<PropertyAccessor> accessorsToTry =
|
|
|
|
|
AstUtils.getPropertyAccessorsToTry(contextObjectClass, this.evaluationContext.getPropertyAccessors());
|
|
|
|
|
if (accessorsToTry != null) {
|
|
|
|
|
for (PropertyAccessor accessor : accessorsToTry) {
|
|
|
|
|
if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) {
|
|
|
|
|
Indexer.this.cachedWriteName = this.name;
|
|
|
|
|
Indexer.this.cachedWriteTargetType = contextObjectClass;
|
|
|
|
|
Indexer.this.cachedWriteAccessor = accessor;
|
|
|
|
|
accessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (AccessException ex) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
|
|
|
|
|
this.name, ex.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
|
|
|
private class CollectionIndexingValueRef implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final Collection collection;
|
|
|
|
|
|
|
|
|
|
private final int index;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor collectionEntryDescriptor;
|
|
|
|
|
|
|
|
|
|
private final TypeConverter typeConverter;
|
|
|
|
|
|
|
|
|
|
private final boolean growCollection;
|
|
|
|
|
|
|
|
|
|
private final int maximumSize;
|
|
|
|
|
|
|
|
|
|
public CollectionIndexingValueRef(Collection collection, int index, TypeDescriptor collectionEntryTypeDescriptor,
|
|
|
|
|
TypeConverter typeConverter, boolean growCollection, int maximumSize) {
|
|
|
|
|
this.collection = collection;
|
|
|
|
|
this.index = index;
|
|
|
|
|
this.collectionEntryDescriptor = collectionEntryTypeDescriptor;
|
|
|
|
|
this.typeConverter = typeConverter;
|
|
|
|
|
this.growCollection = growCollection;
|
|
|
|
|
this.maximumSize = maximumSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
growCollectionIfNecessary();
|
|
|
|
|
if (this.collection instanceof List) {
|
|
|
|
|
Object o = ((List) this.collection).get(this.index);
|
|
|
|
|
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
|
|
|
|
|
}
|
|
|
|
|
int pos = 0;
|
|
|
|
|
for (Object o : this.collection) {
|
|
|
|
|
if (pos == this.index) {
|
|
|
|
|
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
|
|
|
|
|
}
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
throw new IllegalStateException("Failed to find indexed element " + this.index + ": " + this.collection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
growCollectionIfNecessary();
|
|
|
|
|
if (this.collection instanceof List) {
|
|
|
|
|
List list = (List) this.collection;
|
|
|
|
|
if (this.collectionEntryDescriptor.getElementTypeDescriptor() != null) {
|
|
|
|
|
newValue = this.typeConverter.convertValue(newValue, TypeDescriptor.forObject(newValue),
|
|
|
|
|
this.collectionEntryDescriptor.getElementTypeDescriptor());
|
|
|
|
|
}
|
|
|
|
|
list.set(this.index, newValue);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.collectionEntryDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void growCollectionIfNecessary() {
|
|
|
|
|
if (this.index >= this.collection.size()) {
|
|
|
|
|
if (!this.growCollection) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
|
|
|
|
|
this.collection.size(), this.index);
|
|
|
|
|
}
|
|
|
|
|
if(this.index >= this.maximumSize) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
|
|
|
|
}
|
|
|
|
|
if (this.collectionEntryDescriptor.getElementTypeDescriptor() == null) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
|
|
|
|
}
|
|
|
|
|
TypeDescriptor elementType = this.collectionEntryDescriptor.getElementTypeDescriptor();
|
|
|
|
|
try {
|
|
|
|
|
int newElements = this.index - this.collection.size();
|
|
|
|
|
while (newElements >= 0) {
|
|
|
|
|
(this.collection).add(elementType.getType().newInstance());
|
|
|
|
|
newElements--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class StringIndexingLValue implements ValueRef {
|
|
|
|
|
|
|
|
|
|
private final String target;
|
|
|
|
|
|
|
|
|
|
private final int index;
|
|
|
|
|
|
|
|
|
|
private final TypeDescriptor typeDescriptor;
|
|
|
|
|
|
|
|
|
|
public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) {
|
|
|
|
|
this.target = target;
|
|
|
|
|
this.index = index;
|
|
|
|
|
this.typeDescriptor = typeDescriptor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TypedValue getValue() {
|
|
|
|
|
if (this.index >= this.target.length()) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
|
|
|
|
|
this.target.length(), this.index);
|
|
|
|
|
}
|
|
|
|
|
return new TypedValue(String.valueOf(this.target.charAt(this.index)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setValue(Object newValue) {
|
|
|
|
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
|
|
|
|
|
this.typeDescriptor.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean isWritable() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|