Mixed polishing along with recent changes

This commit is contained in:
Juergen Hoeller
2014-02-14 21:39:40 +01:00
parent 9c6df766cd
commit 14e5a02870
9 changed files with 371 additions and 377 deletions

View File

@@ -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.
@@ -30,9 +30,10 @@ import org.springframework.expression.spel.SpelEvaluationException;
public class CompoundExpression extends SpelNodeImpl {
public CompoundExpression(int pos,SpelNodeImpl... expressionComponents) {
super(pos,expressionComponents);
if (expressionComponents.length<2) {
throw new IllegalStateException("Dont build compound expression less than one entry: "+expressionComponents.length);
super(pos, expressionComponents);
if (expressionComponents.length < 2) {
throw new IllegalStateException("Do not build compound expression less than one entry: " +
expressionComponents.length);
}
}
@@ -42,11 +43,9 @@ public class CompoundExpression extends SpelNodeImpl {
if (getChildCount() == 1) {
return this.children[0].getValueRef(state);
}
TypedValue result = null;
SpelNodeImpl nextNode = null;
SpelNodeImpl nextNode = this.children[0];
try {
nextNode = this.children[0];
result = nextNode.getValueInternal(state);
TypedValue result = nextNode.getValueInternal(state);
int cc = getChildCount();
for (int i = 1; i < cc - 1; i++) {
try {
@@ -75,8 +74,8 @@ public class CompoundExpression extends SpelNodeImpl {
}
/**
* Evaluates a compound expression. This involves evaluating each piece in turn and the return value from each piece
* is the active context object for the subsequent piece.
* Evaluates a compound expression. This involves evaluating each piece in turn and the
* return value from each piece is the active context object for the subsequent piece.
* @param state the state in which the expression is being evaluated
* @return the final value from the last piece of the compound expression
*/

View File

@@ -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;
}
}
}

View File

@@ -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.
@@ -37,6 +37,7 @@ public abstract class SpelNodeImpl implements SpelNode {
private static SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0];
protected int pos; // start = top 16bits, end = bottom 16bits
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
@@ -152,22 +153,21 @@ public abstract class SpelNodeImpl implements SpelNode {
return ExpressionUtils.convertTypedValue(state.getEvaluationContext(), getValueInternal(state), desiredReturnType);
}
public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;
@Override
public abstract String toStringAST();
@Override
public int getStartPosition() {
return (this.pos>>16);
return (this.pos >> 16);
}
@Override
public int getEndPosition() {
return (this.pos&0xffff);
return (this.pos & 0xffff);
}
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
throw new SpelEvaluationException(this.pos,SpelMessage.NOT_ASSIGNABLE,toStringAST());
throw new SpelEvaluationException(this.pos, SpelMessage.NOT_ASSIGNABLE, toStringAST());
}
public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;
}

View File

@@ -81,9 +81,19 @@ public class MapAccessTests extends AbstractExpressionTests {
Object bean = new TestBean("name1", new TestBean("name2", null, "Description 2", 15, props1), "description 1", 6, props1);
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("testBean.properties['key2']");
String key = (String) exp.getValue(bean);
assertNotNull(key);
Expression expr = parser.parseExpression("testBean.properties['key2']");
assertEquals("value2", expr.getValue(bean));
}
@Test
public void testGetValueFromRootMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("key", "value");
EvaluationContext context = new StandardEvaluationContext(map);
ExpressionParser spelExpressionParser = new SpelExpressionParser();
Expression expr = spelExpressionParser.parseExpression("#root['key']");
assertEquals("value", expr.getValue(map));
}