Add SpEL support for increment/decrement operators

With this commit the Spring Expression Language now supports
increment (++) and decrement (--) operators. These can be
used as either prefix or postfix operators. For example:
'somearray[index++]' and 'somearray[--index]' are valid.

In order to support this there are serious changes to the
evaluation process for expressions. The concept of a
value reference for an expression component has been introduced.
Value references can be passed around and at any time the actual
value can be retrieved (via a get) or set (where applicable). This
was needed to avoid double evaluation of expression components.
For example, in evaluating the expression 'somearray[index++]--'
without a value reference SpEL would need to evaluate the
'somearray[index++]' component twice, once to get the value and
then again to determine where to put the new value. If that
component is evaluated twice, index would be double incremented.
A value reference for 'somearray[index++]' avoids this problem.

Many new tests have been introduced into the EvaluationTests
to ensure not only that ++ and -- work but also that the
introduction of value references across the all of SpEL has
not caused regressions.

Issue: SPR-9751
This commit is contained in:
Andy Clement
2012-10-23 12:00:22 -07:00
committed by Chris Beams
parent 33d37e8680
commit f64325882d
19 changed files with 1838 additions and 674 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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.
@@ -28,6 +28,7 @@ import org.springframework.expression.Operation;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
/**
@@ -140,6 +141,10 @@ public class ExpressionState {
return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor);
}
public TypeConverter getTypeConverter() {
return this.relatedContext.getTypeConverter();
}
public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
Object val = value.getValue();
return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor);

View File

@@ -104,7 +104,10 @@ public enum SpelMessage {
MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), //
INITIALIZER_LENGTH_INCORRECT(
Kind.ERROR, 1064, "array initializer size does not match array dimensions"), //
UNEXPECTED_ESCAPE_CHAR(Kind.ERROR,1065,"unexpected escape character.");
UNEXPECTED_ESCAPE_CHAR(Kind.ERROR,1065,"unexpected escape character."), //
OPERAND_NOT_INCREMENTABLE(Kind.ERROR,1066,"the expression component ''{0}'' does not support increment"), //
OPERAND_NOT_DECREMENTABLE(Kind.ERROR,1067,"the expression component ''{0}'' does not support decrement"), //
NOT_ASSIGNABLE(Kind.ERROR,1068,"the expression component ''{0}'' is not assignable"), //
;
private Kind kind;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010 the original author or authors.
* Copyright 2010-2012 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.
@@ -41,10 +41,10 @@ public class AstUtils {
* @param targetType the type upon which property access is being attempted
* @return a list of resolvers that should be tried in order to access the property
*/
public static List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, ExpressionState state) {
public static List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, List<PropertyAccessor> propertyAccessors) {
List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
for (PropertyAccessor resolver : state.getPropertyAccessors()) {
for (PropertyAccessor resolver : propertyAccessors) {
Class<?>[] targets = resolver.getSpecificTargetClasses();
if (targets == null) { // generic resolver that says it can be used for any type
generalAccessors.add(resolver);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@@ -35,22 +35,19 @@ public class CompoundExpression extends SpelNodeImpl {
throw new IllegalStateException("Dont build compound expression less than one entry: "+expressionComponents.length);
}
}
/**
* Evalutes 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
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
if (getChildCount()==1) {
return children[0].getValueRef(state);
}
TypedValue result = null;
SpelNodeImpl nextNode = null;
try {
nextNode = children[0];
result = nextNode.getValueInternal(state);
for (int i = 1; i < getChildCount(); i++) {
int cc = getChildCount();
for (int i = 1; i < cc-1; i++) {
try {
state.pushActiveContextObject(result);
nextNode = children[i];
@@ -59,57 +56,39 @@ public class CompoundExpression extends SpelNodeImpl {
state.popActiveContextObject();
}
}
try {
state.pushActiveContextObject(result);
nextNode = children[cc-1];
return nextNode.getValueRef(state);
} finally {
state.popActiveContextObject();
}
} catch (SpelEvaluationException ee) {
// Correct the position for the error before rethrowing
// Correct the position for the error before re-throwing
ee.setPosition(nextNode.getStartPosition());
throw ee;
}
return result;
}
/**
* 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
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return getValueRef(state).getValue();
}
@Override
public void setValue(ExpressionState state, Object value) throws EvaluationException {
if (getChildCount() == 1) {
getChild(0).setValue(state, value);
return;
}
TypedValue ctx = children[0].getValueInternal(state);
for (int i = 1; i < getChildCount() - 1; i++) {
try {
state.pushActiveContextObject(ctx);
ctx = children[i].getValueInternal(state);
} finally {
state.popActiveContextObject();
}
}
try {
state.pushActiveContextObject(ctx);
getChild(getChildCount() - 1).setValue(state, value);
} finally {
state.popActiveContextObject();
}
getValueRef(state).setValue(value);
}
@Override
public boolean isWritable(ExpressionState state) throws EvaluationException {
if (getChildCount() == 1) {
return getChild(0).isWritable(state);
}
TypedValue ctx = children[0].getValueInternal(state);
for (int i = 1; i < getChildCount() - 1; i++) {
try {
state.pushActiveContextObject(ctx);
ctx = children[i].getValueInternal(state);
} finally {
state.popActiveContextObject();
}
}
try {
state.pushActiveContextObject(ctx);
return getChild(getChildCount() - 1).isWritable(state);
} finally {
state.popActiveContextObject();
}
return getValueRef(state).isWritable();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -25,6 +25,7 @@ import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
@@ -32,8 +33,9 @@ import org.springframework.expression.spel.SpelMessage;
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
* An Indexer can index into some proceeding structure to access a particular
* piece of it. Supported structures are: strings/collections
* (lists/sets)/arrays
*
* @author Andy Clement
* @since 3.0
@@ -42,20 +44,21 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
// TODO support correct syntax for multidimensional [][][] and not [,,,]
public class Indexer extends SpelNodeImpl {
// These fields are used when the indexer is being used as a property read accessor. 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 discovered and then cached for later use.
// These fields are used when the indexer is being used as a property read accessor.
// 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
// discovered and then cached for later use.
private String cachedReadName;
private Class<?> cachedReadTargetType;
private PropertyAccessor cachedReadAccessor;
// 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 is used to write the property.
// If they do not match, the correct accessor is discovered and then cached for later use.
// 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
// is used to write the property. If they do not match, the correct accessor is
// discovered and then cached for later use.
private String cachedWriteName;
private Class<?> cachedWriteTargetType;
private PropertyAccessor cachedWriteAccessor;
public Indexer(int pos, SpelNodeImpl expr) {
super(pos, expr);
@@ -63,27 +66,313 @@ public class Indexer extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return getValueRef(state).getValue();
}
@Override
public void setValue(ExpressionState state, Object newValue) throws EvaluationException {
getValueRef(state).setValue(newValue);
}
@Override
public boolean isWritable(ExpressionState expressionState)
throws SpelEvaluationException {
return true;
}
class ArrayIndexingValueRef implements ValueRef {
private TypeConverter typeConverter;
private Object array;
private int idx;
private TypeDescriptor typeDescriptor;
ArrayIndexingValueRef(TypeConverter typeConverter, Object array,
int idx, TypeDescriptor typeDescriptor) {
this.typeConverter = typeConverter;
this.array = array;
this.idx = idx;
this.typeDescriptor = typeDescriptor;
}
public TypedValue getValue() {
Object arrayElement = accessArrayElement(array, idx);
return new TypedValue(arrayElement,
typeDescriptor.elementTypeDescriptor(arrayElement));
}
public void setValue(Object newValue) {
setArrayElement(typeConverter, array, idx, newValue, typeDescriptor
.getElementTypeDescriptor().getType());
}
public boolean isWritable() {
return true;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
class MapIndexingValueRef implements ValueRef {
private TypeConverter typeConverter;
private Map map;
private Object key;
private TypeDescriptor mapEntryTypeDescriptor;
MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key,
TypeDescriptor mapEntryTypeDescriptor) {
this.typeConverter = typeConverter;
this.map = map;
this.key = key;
this.mapEntryTypeDescriptor = mapEntryTypeDescriptor;
}
public TypedValue getValue() {
Object value = map.get(key);
return new TypedValue(value,
mapEntryTypeDescriptor.getMapValueTypeDescriptor(value));
}
public void setValue(Object newValue) {
if (mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) {
newValue = typeConverter.convertValue(newValue,
TypeDescriptor.forObject(newValue),
mapEntryTypeDescriptor.getMapValueTypeDescriptor());
}
map.put(key, newValue);
}
public boolean isWritable() {
return true;
}
}
class PropertyIndexingValueRef implements ValueRef {
private Object targetObject;
private String name;
private EvaluationContext eContext;
private TypeDescriptor td;
public PropertyIndexingValueRef(Object targetObject, String value,
EvaluationContext evaluationContext,
TypeDescriptor targetObjectTypeDescriptor) {
this.targetObject = targetObject;
this.name = value;
this.eContext = evaluationContext;
this.td = targetObjectTypeDescriptor;
}
public TypedValue getValue() {
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
try {
if (cachedReadName != null
&& cachedReadName.equals(name)
&& cachedReadTargetType != null
&& cachedReadTargetType
.equals(targetObjectRuntimeClass)) {
// it is OK to use the cached accessor
return cachedReadAccessor
.read(eContext, targetObject, name);
}
List<PropertyAccessor> accessorsToTry = AstUtils
.getPropertyAccessorsToTry(targetObjectRuntimeClass,
eContext.getPropertyAccessors());
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canRead(eContext, targetObject, name)) {
if (accessor instanceof ReflectivePropertyAccessor) {
accessor = ((ReflectivePropertyAccessor) accessor)
.createOptimalAccessor(eContext,
targetObject, name);
}
cachedReadAccessor = accessor;
cachedReadName = name;
cachedReadTargetType = targetObjectRuntimeClass;
return accessor.read(eContext, targetObject, name);
}
}
}
} catch (AccessException e) {
throw new SpelEvaluationException(getStartPosition(), e,
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
td.toString());
}
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, td.toString());
}
public void setValue(Object newValue) {
Class<?> contextObjectClass = getObjectClass(targetObject);
try {
if (cachedWriteName != null && cachedWriteName.equals(name)
&& cachedWriteTargetType != null
&& cachedWriteTargetType.equals(contextObjectClass)) {
// it is OK to use the cached accessor
cachedWriteAccessor.write(eContext, targetObject, name,
newValue);
return;
}
List<PropertyAccessor> accessorsToTry = AstUtils
.getPropertyAccessorsToTry(contextObjectClass,
eContext.getPropertyAccessors());
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(eContext, targetObject, name)) {
cachedWriteName = name;
cachedWriteTargetType = contextObjectClass;
cachedWriteAccessor = accessor;
accessor.write(eContext, targetObject, name,
newValue);
return;
}
}
}
} catch (AccessException ae) {
throw new SpelEvaluationException(getStartPosition(), ae,
SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE, name,
ae.getMessage());
}
}
public boolean isWritable() {
return true;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
class CollectionIndexingValueRef implements ValueRef {
private TypeConverter typeConverter;
private Collection collection;
private int index;
private TypeDescriptor collectionEntryTypeDescriptor;
private boolean growCollection;
CollectionIndexingValueRef(Collection collection, int index,
TypeDescriptor collectionEntryTypeDescriptor, TypeConverter typeConverter, boolean growCollection) {
this.typeConverter = typeConverter;
this.growCollection = growCollection;
this.collection = collection;
this.index = index;
this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor;
}
public TypedValue getValue() {
if (index >= collection.size()) {
if (growCollection) {
growCollection(collectionEntryTypeDescriptor,index,collection);
} else {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
collection.size(), index);
}
}
int pos = 0;
for (Object o : collection) {
if (pos == index) {
return new TypedValue(o,
collectionEntryTypeDescriptor
.elementTypeDescriptor(o));
}
pos++;
}
throw new IllegalStateException();
}
public void setValue(Object newValue) {
if (index >= collection.size()) {
if (growCollection) {
growCollection(collectionEntryTypeDescriptor, index, collection);
} else {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,
collection.size(), index);
}
}
if (collection instanceof List) {
List list = (List) collection;
if (collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) {
newValue = typeConverter.convertValue(newValue,
TypeDescriptor.forObject(newValue),
collectionEntryTypeDescriptor
.getElementTypeDescriptor());
}
list.set(index, newValue);
return;
} else {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
collectionEntryTypeDescriptor.toString());
}
}
public boolean isWritable() {
return true;
}
}
class StringIndexingLValue implements ValueRef {
private String target;
private int index;
private TypeDescriptor td;
public StringIndexingLValue(String target, int index, TypeDescriptor td) {
this.target = target;
this.index = index;
this.td = td;
}
public TypedValue getValue() {
if (index >= target.length()) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.STRING_INDEX_OUT_OF_BOUNDS,
target.length(), index);
}
return new TypedValue(String.valueOf(target.charAt(index)));
}
public void setValue(Object newValue) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, td.toString());
}
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();
TypedValue indexValue = null;
Object index = null;
// This first part of the if clause prevents a 'double dereference' of the property (SPR-5847)
// This first part of the if clause prevents a 'double dereference' of
// the property (SPR-5847)
if (targetObject instanceof Map && (children[0] instanceof PropertyOrFieldReference)) {
PropertyOrFieldReference reference = (PropertyOrFieldReference)children[0];
PropertyOrFieldReference reference = (PropertyOrFieldReference) 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
} 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 = children[0].getValueInternal(state);
index = indexValue.getValue();
}
finally {
} finally {
state.popActiveContextObject();
}
}
@@ -92,207 +381,83 @@ public class Indexer extends SpelNodeImpl {
if (targetObject instanceof Map) {
Object key = index;
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
key = state.convertValue(key,
targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
}
Object value = ((Map<?, ?>) targetObject).get(key);
return new TypedValue(value, targetObjectTypeDescriptor.getMapValueTypeDescriptor(value));
return new MapIndexingValueRef(state.getTypeConverter(),
(Map<?, ?>) targetObject, key, targetObjectTypeDescriptor);
}
if (targetObject == null) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
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 instanceof Collection || targetObject.getClass().isArray() || targetObject instanceof String) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
// if the object is something that looks indexable by an integer,
// attempt to treat the index value as a number
if (targetObject instanceof Collection
|| targetObject.getClass().isArray()
|| targetObject instanceof String) {
int idx = (Integer) state.convertValue(index,
TypeDescriptor.valueOf(Integer.class));
if (targetObject.getClass().isArray()) {
Object arrayElement = accessArrayElement(targetObject, idx);
return new TypedValue(arrayElement, targetObjectTypeDescriptor.elementTypeDescriptor(arrayElement));
return new ArrayIndexingValueRef(state.getTypeConverter(),
targetObject, idx, targetObjectTypeDescriptor);
} else if (targetObject instanceof Collection) {
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
int pos = 0;
for (Object o : c) {
if (pos == idx) {
return new TypedValue(o, targetObjectTypeDescriptor.elementTypeDescriptor(o));
}
pos++;
}
return new CollectionIndexingValueRef(
(Collection<?>) targetObject, idx,
targetObjectTypeDescriptor,state.getTypeConverter(),
state.getConfiguration().isAutoGrowCollections());
} else if (targetObject instanceof String) {
String ctxString = (String) targetObject;
if (idx >= ctxString.length()) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, ctxString.length(), idx);
}
return new TypedValue(String.valueOf(ctxString.charAt(idx)));
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) {
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
String name = (String)indexValue.getValue();
EvaluationContext eContext = state.getEvaluationContext();
try {
if (cachedReadName!=null && cachedReadName.equals(name) && cachedReadTargetType!=null && cachedReadTargetType.equals(targetObjectRuntimeClass)) {
// it is OK to use the cached accessor
return cachedReadAccessor.read(eContext, targetObject, name);
}
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(targetObjectRuntimeClass, state);
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canRead(eContext, targetObject, name)) {
if (accessor instanceof ReflectivePropertyAccessor) {
accessor = ((ReflectivePropertyAccessor)accessor).createOptimalAccessor(eContext, targetObject, name);
}
this.cachedReadAccessor = accessor;
this.cachedReadName = name;
this.cachedReadTargetType = targetObjectRuntimeClass;
return accessor.read(eContext, targetObject, name);
}
}
}
} catch (AccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
// 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());
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
targetObjectTypeDescriptor.toString());
}
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
return true;
}
@SuppressWarnings("unchecked")
@Override
public void setValue(ExpressionState state, Object newValue) throws EvaluationException {
TypedValue contextObject = state.getActiveContextObject();
Object targetObject = contextObject.getValue();
TypeDescriptor targetObjectTypeDescriptor = contextObject.getTypeDescriptor();
TypedValue index = children[0].getValueInternal(state);
if (targetObject == null) {
throw new SpelEvaluationException(SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
// Indexing into a Map
if (targetObject instanceof Map) {
Map map = (Map) targetObject;
Object key = index.getValue();
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
key = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
}
if (targetObjectTypeDescriptor.getMapValueTypeDescriptor() != null) {
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
}
map.put(key, newValue);
return;
}
if (targetObjectTypeDescriptor.isArray()) {
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementTypeDescriptor().getType());
return;
}
else if (targetObject instanceof Collection) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
Collection c = (Collection) targetObject;
if (idx >= c.size()) {
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
}
}
if (targetObject instanceof List) {
List list = (List) targetObject;
if (targetObjectTypeDescriptor.getElementTypeDescriptor() != null) {
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementTypeDescriptor());
}
list.set(idx, newValue);
return;
}
else {
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
}
// 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 (index.getTypeDescriptor().getType() == String.class) {
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
String name = (String)index.getValue();
EvaluationContext eContext = state.getEvaluationContext();
try {
if (cachedWriteName!=null && cachedWriteName.equals(name) && cachedWriteTargetType!=null && cachedWriteTargetType.equals(contextObjectClass)) {
// it is OK to use the cached accessor
cachedWriteAccessor.write(eContext, targetObject, name,newValue);
return;
}
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(contextObjectClass, state);
if (accessorsToTry != null) {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(eContext, contextObject.getValue(), name)) {
this.cachedWriteName = name;
this.cachedWriteTargetType = contextObjectClass;
this.cachedWriteAccessor = accessor;
accessor.write(eContext, contextObject.getValue(), name, newValue);
return;
}
}
}
} catch (AccessException ae) {
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE,
name, ae.getMessage());
}
}
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
}
/**
* Attempt to grow the specified collection so that the specified index is valid.
*
* @param state the expression state
* Attempt to grow the specified collection so that the specified index is
* valid.
*
* @param elementType the type of the elements in the collection
* @param index the index into the collection that needs to be valid
* @param collection the collection to grow with elements
* @return true if collection growing succeeded, otherwise false
*/
@SuppressWarnings("unchecked")
private boolean growCollection(ExpressionState state, TypeDescriptor targetType, int index,
Collection collection) {
if (state.getConfiguration().isAutoGrowCollections()) {
if (targetType.getElementTypeDescriptor() == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
}
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
Object newCollectionElement = null;
try {
int newElements = index - collection.size();
while (newElements>0) {
collection.add(elementType.getType().newInstance());
newElements--;
}
newCollectionElement = elementType.getType().newInstance();
}
catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
}
collection.add(newCollectionElement);
return true;
private void growCollection(TypeDescriptor targetType, int index, Collection<Object> collection) {
if (targetType.getElementTypeDescriptor() == null) {
throw new SpelEvaluationException(
getStartPosition(),
SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
}
return false;
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
Object newCollectionElement = null;
try {
int newElements = index - collection.size();
while (newElements > 0) {
collection.add(elementType.getType().newInstance());
newElements--;
}
newCollectionElement = elementType.getType().newInstance();
} catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex,
SpelMessage.UNABLE_TO_GROW_COLLECTION);
}
collection.add(newCollectionElement);
}
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder();
@@ -306,47 +471,66 @@ public class Indexer extends SpelNodeImpl {
return sb.toString();
}
private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue, Class clazz) throws EvaluationException {
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)state.convertValue(newValue, TypeDescriptor.valueOf(Integer.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Boolean.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Character.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Long.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Short.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Double.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Float.class));
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)state.convertValue(newValue, TypeDescriptor.valueOf(Byte.class));
array[idx] = (Byte) converter.convertValue(newValue,
TypeDescriptor.forObject(newValue),
TypeDescriptor.valueOf(Byte.class));
} else {
Object[] array = (Object[]) ctx;
checkAccess(array.length, idx);
array[idx] = state.convertValue(newValue, TypeDescriptor.valueOf(clazz));
}
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) {
@@ -390,8 +574,9 @@ public class Indexer extends SpelNodeImpl {
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
if (index > arrayLength) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index);
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@@ -52,6 +52,94 @@ public class MethodReference extends SpelNodeImpl {
this.nullSafe = nullSafe;
}
class MethodValueRef implements ValueRef {
private ExpressionState state;
private EvaluationContext evaluationContext;
private Object target;
private Object[] arguments;
MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, Object object, Object[] arguments) {
this.state = state;
this.evaluationContext = evaluationContext;
this.target = object;
this.arguments = arguments;
}
public TypedValue getValue() {
MethodExecutor executorToUse = cachedExecutor;
if (executorToUse != null) {
try {
return executorToUse.execute(evaluationContext, target, arguments);
}
catch (AccessException ae) {
// Two reasons this can occur:
// 1. the method invoked actually threw a real exception
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
// In the first case we should not retry, in the second case we should see if there is a
// better suited method.
// To determine which situation it is, the AccessException will contain a cause.
// If the cause is an InvocationTargetException, a user exception was thrown inside the method.
// Otherwise the method could not be invoked.
throwSimpleExceptionIfPossible(state, ae);
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
cachedExecutor = null;
}
}
// either there was no accessor or it no longer existed
executorToUse = findAccessorForMethod(name, getTypes(arguments), target, evaluationContext);
cachedExecutor = executorToUse;
try {
return executorToUse.execute(evaluationContext, target, arguments);
} catch (AccessException ae) {
// Same unwrapping exception handling as above in above catch block
throwSimpleExceptionIfPossible(state, ae);
throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION,
name, state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage());
}
}
public void setValue(Object newValue) {
throw new IllegalAccessError();
}
public boolean isWritable() {
return false;
}
}
@Override
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
TypedValue currentContext = state.getActiveContextObject();
Object[] arguments = new Object[getChildCount()];
for (int i = 0; i < arguments.length; i++) {
// Make the root object the active context again for evaluating the parameter
// expressions
try {
state.pushActiveContextObject(state.getRootContextObject());
arguments[i] = children[i].getValueInternal(state).getValue();
}
finally {
state.popActiveContextObject();
}
}
if (currentContext.getValue() == null) {
if (nullSafe) {
return ValueRef.NullValueRef.instance;
}
else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,
FormatHelper.formatMethodForMessage(name, getTypes(arguments)));
}
}
return new MethodValueRef(state,state.getEvaluationContext(),state.getActiveContextObject().getValue(),arguments);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
@@ -65,7 +153,7 @@ public class MethodReference extends SpelNodeImpl {
arguments[i] = children[i].getValueInternal(state).getValue();
}
finally {
state.popActiveContextObject();
state.popActiveContextObject();
}
}
if (currentContext.getValue() == null) {
@@ -88,15 +176,15 @@ public class MethodReference extends SpelNodeImpl {
// Two reasons this can occur:
// 1. the method invoked actually threw a real exception
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
// In the first case we should not retry, in the second case we should see if there is a
// In the first case we should not retry, in the second case we should see if there is a
// better suited method.
// To determine which situation it is, the AccessException will contain a cause.
// If the cause is an InvocationTargetException, a user exception was thrown inside the method.
// Otherwise the method could not be invoked.
throwSimpleExceptionIfPossible(state, ae);
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
this.cachedExecutor = null;
}
@@ -118,7 +206,7 @@ public class MethodReference extends SpelNodeImpl {
/**
* Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException,
* Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException,
* throw the RuntimeException directly.
*/
private void throwSimpleExceptionIfPossible(ExpressionState state, AccessException ae) {
@@ -132,7 +220,7 @@ public class MethodReference extends SpelNodeImpl {
"A problem occurred when trying to execute method '" + this.name +
"' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'",
rootCause);
}
}
}
}
@@ -157,19 +245,22 @@ public class MethodReference extends SpelNodeImpl {
return sb.toString();
}
private MethodExecutor findAccessorForMethod(String name, List<TypeDescriptor> argumentTypes, ExpressionState state)
private MethodExecutor findAccessorForMethod(String name,
List<TypeDescriptor> argumentTypes, ExpressionState state)
throws SpelEvaluationException {
return findAccessorForMethod(name,argumentTypes,state.getActiveContextObject().getValue(),state.getEvaluationContext());
}
TypedValue context = state.getActiveContextObject();
Object contextObject = context.getValue();
EvaluationContext eContext = state.getEvaluationContext();
private MethodExecutor findAccessorForMethod(String name,
List<TypeDescriptor> argumentTypes, Object contextObject, EvaluationContext eContext)
throws SpelEvaluationException {
List<MethodResolver> mResolvers = eContext.getMethodResolvers();
if (mResolvers != null) {
for (MethodResolver methodResolver : mResolvers) {
try {
MethodExecutor cEx = methodResolver.resolve(
state.getEvaluationContext(), contextObject, name, argumentTypes);
eContext, contextObject, name, argumentTypes);
if (cEx != null) {
return cEx;
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2002-2012 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
/**
* Decrement operator. Can be used in a prefix or postfix form. This will throw
* appropriate exceptions if the operand in question does not support decrement.
*
* @author Andy Clement
* @since 3.2
*/
public class OpDec extends Operator {
private boolean postfix; // false means prefix
public OpDec(int pos, boolean postfix, SpelNodeImpl... operands) {
super("--", pos, operands);
Assert.notEmpty(operands);
this.postfix = postfix;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl operand = getLeftOperand();
// The operand is going to be read and then assigned to, we don't want to evaluate it twice.
ValueRef lvalue = operand.getValueRef(state);
final TypedValue operandTypedValue = lvalue.getValue();//operand.getValueInternal(state);
final Object operandValue = operandTypedValue.getValue();
TypedValue returnValue = operandTypedValue;
TypedValue newValue = null;
if (operandValue instanceof Number) {
Number op1 = (Number) operandValue;
if (op1 instanceof Double) {
newValue = new TypedValue(op1.doubleValue() - 1.0d, operandTypedValue.getTypeDescriptor());
} else if (op1 instanceof Float) {
newValue = new TypedValue(op1.floatValue() - 1.0f, operandTypedValue.getTypeDescriptor());
} else if (op1 instanceof Long) {
newValue = new TypedValue(op1.longValue() - 1L, operandTypedValue.getTypeDescriptor());
} else if (op1 instanceof Short) {
newValue = new TypedValue(op1.shortValue() - (short)1, operandTypedValue.getTypeDescriptor());
} else {
newValue = new TypedValue(op1.intValue() - 1, operandTypedValue.getTypeDescriptor());
}
}
if (newValue==null) {
try {
newValue = state.operate(Operation.SUBTRACT, returnValue.getValue(), 1);
} catch (SpelEvaluationException see) {
if (see.getMessageCode()==SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) {
// This means the operand is not decrementable
throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_DECREMENTABLE,operand.toStringAST());
} else {
throw see;
}
}
}
// set the new value
try {
lvalue.setValue(newValue.getValue());
} catch (SpelEvaluationException see) {
// if unable to set the value the operand is not writable (e.g. 1-- )
if (see.getMessageCode()==SpelMessage.SETVALUE_NOT_SUPPORTED) {
throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_DECREMENTABLE);
} else {
throw see;
}
}
if (!postfix) {
// the return value is the new value, not the original value
returnValue = newValue;
}
return returnValue;
}
@Override
public String toStringAST() {
return new StringBuilder().append(getLeftOperand().toStringAST()).append("--").toString();
}
@Override
public SpelNodeImpl getRightOperand() {
return null;
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2002-2012 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
/**
* Increment operator. Can be used in a prefix or postfix form. This will throw
* appropriate exceptions if the operand in question does not support increment.
*
* @author Andy Clement
* @since 3.2
*/
public class OpInc extends Operator {
private boolean postfix; // false means prefix
public OpInc(int pos, boolean postfix, SpelNodeImpl... operands) {
super("++", pos, operands);
Assert.notEmpty(operands);
this.postfix = postfix;
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
SpelNodeImpl operand = getLeftOperand();
ValueRef lvalue = operand.getValueRef(state);
final TypedValue operandTypedValue = lvalue.getValue();
final Object operandValue = operandTypedValue.getValue();
TypedValue returnValue = operandTypedValue;
TypedValue newValue = null;
if (operandValue instanceof Number) {
Number op1 = (Number) operandValue;
if (op1 instanceof Double) {
newValue = new TypedValue(op1.doubleValue() + 1.0d, operandTypedValue.getTypeDescriptor());
} else if (op1 instanceof Float) {
newValue = new TypedValue(op1.floatValue() + 1.0f, operandTypedValue.getTypeDescriptor());
} else if (op1 instanceof Long) {
newValue = new TypedValue(op1.longValue() + 1L, operandTypedValue.getTypeDescriptor());
} else if (op1 instanceof Short) {
newValue = new TypedValue(op1.shortValue() + (short)1, operandTypedValue.getTypeDescriptor());
} else {
newValue = new TypedValue(op1.intValue() + 1, operandTypedValue.getTypeDescriptor());
}
}
if (newValue==null) {
try {
newValue = state.operate(Operation.ADD, returnValue.getValue(), 1);
} catch (SpelEvaluationException see) {
if (see.getMessageCode()==SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES) {
// This means the operand is not incrementable
throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_INCREMENTABLE,operand.toStringAST());
} else {
throw see;
}
}
}
// set the name value
try {
lvalue.setValue(newValue.getValue());
} catch (SpelEvaluationException see) {
// if unable to set the value the operand is not writable (e.g. 1++ )
if (see.getMessageCode()==SpelMessage.SETVALUE_NOT_SUPPORTED) {
throw new SpelEvaluationException(operand.getStartPosition(),SpelMessage.OPERAND_NOT_INCREMENTABLE);
} else {
throw see;
}
}
if (!postfix) {
// the return value is the new value, not the original value
returnValue = newValue;
}
return returnValue;
}
@Override
public String toStringAST() {
return new StringBuilder().append(getLeftOperand().toStringAST()).append("++").toString();
}
@Override
public SpelNodeImpl getRightOperand() {
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -51,6 +51,11 @@ public class Projection extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return getValueRef(state).getValue();
}
@Override
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
TypedValue op = state.getActiveContextObject();
Object operand = op.getValue();
@@ -65,7 +70,7 @@ public class Projection extends SpelNodeImpl {
if (operand instanceof Map) {
Map<?, ?> mapData = (Map<?, ?>) operand;
List<Object> result = new ArrayList<Object>();
for (Map.Entry entry : mapData.entrySet()) {
for (Map.Entry<?,?> entry : mapData.entrySet()) {
try {
state.pushActiveContextObject(new TypedValue(entry));
result.add(this.children[0].getValueInternal(state).getValue());
@@ -74,7 +79,7 @@ public class Projection extends SpelNodeImpl {
state.popActiveContextObject();
}
}
return new TypedValue(result); // TODO unable to build correct type descriptor
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); // TODO unable to build correct type descriptor
}
else if (operand instanceof Collection || operandIsArray) {
Collection<?> data = (operand instanceof Collection ? (Collection<?>) operand :
@@ -104,14 +109,14 @@ public class Projection extends SpelNodeImpl {
}
Object resultArray = Array.newInstance(arrayElementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
}
return new TypedValue(result);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
}
else {
if (operand==null) {
if (this.nullSafe) {
return TypedValue.NULL;
return ValueRef.NullValueRef.instance;
}
else {
throw new SpelEvaluationException(getStartPosition(),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -49,7 +49,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
private volatile PropertyAccessor cachedReadAccessor;
private volatile PropertyAccessor cachedWriteAccessor;
public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) {
super(pos);
@@ -67,12 +67,52 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
static class AccessorLValue implements ValueRef {
private PropertyOrFieldReference ref;
private TypedValue contextObject;
private EvaluationContext eContext;
private boolean isAutoGrowNullReferences;
public AccessorLValue(
PropertyOrFieldReference propertyOrFieldReference,
TypedValue activeContextObject,
EvaluationContext evaluationContext, boolean isAutoGrowNullReferences) {
this.ref = propertyOrFieldReference;
this.contextObject = activeContextObject;
this.eContext =evaluationContext;
this.isAutoGrowNullReferences = isAutoGrowNullReferences;
}
public TypedValue getValue() {
return ref.getValueInternal(contextObject,eContext,isAutoGrowNullReferences);
}
public void setValue(Object newValue) {
ref.writeProperty(contextObject,eContext, ref.name, newValue);
}
public boolean isWritable() {
return true;
}
}
@Override
public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
return new AccessorLValue(this,state.getActiveContextObject(),state.getEvaluationContext(),state.getConfiguration().isAutoGrowNullReferences());
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue result = readProperty(state, this.name);
// Dynamically create the objects if the user has requested that optional behaviour
if (result.getValue() == null && state.getConfiguration().isAutoGrowNullReferences() &&
return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences());
}
private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext, boolean isAutoGrowNullReferences) throws EvaluationException {
TypedValue result = readProperty(contextObject, eContext, this.name);
// Dynamically create the objects if the user has requested that optional behavior
if (result.getValue() == null && isAutoGrowNullReferences &&
nextChildIs(Indexer.class, PropertyOrFieldReference.class)) {
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
// Creating lists and maps
@@ -80,10 +120,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
// Create a new collection or map ready for the indexer
if (resultDescriptor.getType().equals(List.class)) {
try {
if (isWritable(state)) {
List newList = ArrayList.class.newInstance();
writeProperty(state, this.name, newList);
result = readProperty(state, this.name);
if (isWritableProperty(this.name,contextObject,eContext)) {
List<?> newList = ArrayList.class.newInstance();
writeProperty(contextObject, eContext, this.name, newList);
result = readProperty(contextObject, eContext, this.name);
}
}
catch (InstantiationException ex) {
@@ -97,10 +137,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
else {
try {
if (isWritable(state)) {
Map newMap = HashMap.class.newInstance();
writeProperty(state, name, newMap);
result = readProperty(state, this.name);
if (isWritableProperty(this.name,contextObject,eContext)) {
Map<?,?> newMap = HashMap.class.newInstance();
writeProperty(contextObject, eContext, name, newMap);
result = readProperty(contextObject, eContext, this.name);
}
}
catch (InstantiationException ex) {
@@ -116,10 +156,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
else {
// 'simple' object
try {
if (isWritable(state)) {
if (isWritableProperty(this.name,contextObject,eContext)) {
Object newObject = result.getTypeDescriptor().getType().newInstance();
writeProperty(state, name, newObject);
result = readProperty(state, this.name);
writeProperty(contextObject, eContext, name, newObject);
result = readProperty(contextObject, eContext, this.name);
}
}
catch (InstantiationException ex) {
@@ -137,12 +177,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public void setValue(ExpressionState state, Object newValue) throws SpelEvaluationException {
writeProperty(state, this.name, newValue);
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue);
}
@Override
public boolean isWritable(ExpressionState state) throws SpelEvaluationException {
return isWritableProperty(this.name, state);
return isWritableProperty(this.name, state.getActiveContextObject(), state.getEvaluationContext());
}
@Override
@@ -157,8 +197,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
* @return the value of the property
* @throws SpelEvaluationException if any problem accessing the property or it cannot be found
*/
private TypedValue readProperty(ExpressionState state, String name) throws EvaluationException {
TypedValue contextObject = state.getActiveContextObject();
private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException {
Object targetObject = contextObject.getValue();
if (targetObject == null && this.nullSafe) {
@@ -168,7 +207,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
PropertyAccessor accessorToUse = this.cachedReadAccessor;
if (accessorToUse != null) {
try {
return accessorToUse.read(state.getEvaluationContext(), contextObject.getValue(), name);
return accessorToUse.read(eContext, contextObject.getValue(), name);
}
catch (AccessException ae) {
// this is OK - it may have gone stale due to a class change,
@@ -178,8 +217,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
EvaluationContext eContext = state.getEvaluationContext();
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors());
// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
// get the accessor and use it. If they are not cacheable but report they can read the property
@@ -210,9 +248,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
}
private void writeProperty(ExpressionState state, String name, Object newValue) throws SpelEvaluationException {
TypedValue contextObject = state.getActiveContextObject();
EvaluationContext eContext = state.getEvaluationContext();
private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException {
if (contextObject.getValue() == null && nullSafe) {
return;
@@ -220,8 +256,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
PropertyAccessor accessorToUse = this.cachedWriteAccessor;
if (accessorToUse != null) {
try {
accessorToUse.write(state.getEvaluationContext(), contextObject.getValue(), name, newValue);
try {
accessorToUse.write(eContext, contextObject.getValue(), name, newValue);
return;
}
catch (AccessException ae) {
@@ -233,7 +269,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors());
if (accessorsToTry != null) {
try {
for (PropertyAccessor accessor : accessorsToTry) {
@@ -258,15 +294,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
}
}
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
Object contextObject = state.getActiveContextObject().getValue();
public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext eContext) throws SpelEvaluationException {
Object contextObjectValue = contextObject.getValue();
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
EvaluationContext eContext = state.getEvaluationContext();
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject), state);
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObjectValue), eContext.getPropertyAccessors());
if (resolversToTry != null) {
for (PropertyAccessor pfResolver : resolversToTry) {
try {
if (pfResolver.canWrite(eContext, contextObject, name)) {
if (pfResolver.canWrite(eContext, contextObjectValue, name)) {
return true;
}
}
@@ -289,10 +324,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
* @param targetType the type upon which property access is being attempted
* @return a list of resolvers that should be tried in order to access the property
*/
private List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, ExpressionState state) {
private List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, List<PropertyAccessor> propertyAccessors) {
List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
for (PropertyAccessor resolver : state.getPropertyAccessors()) {
for (PropertyAccessor resolver : propertyAccessors) {
Class<?>[] targets = resolver.getSpecificTargetClasses();
if (targets == null) { // generic resolver that says it can be used for any type
generalAccessors.add(resolver);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -59,19 +59,23 @@ public class Selection extends SpelNodeImpl {
this.variant = variant;
}
@SuppressWarnings("unchecked")
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return getValueRef(state).getValue();
}
@Override
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
TypedValue op = state.getActiveContextObject();
Object operand = op.getValue();
SpelNodeImpl selectionCriteria = children[0];
if (operand instanceof Map) {
Map<?, ?> mapdata = (Map<?, ?>) operand;
// TODO don't lose generic info for the new map
Map<Object,Object> result = new HashMap<Object,Object>();
Object lastKey = null;
for (Map.Entry entry : mapdata.entrySet()) {
for (Map.Entry<?,?> entry : mapdata.entrySet()) {
try {
TypedValue kvpair = new TypedValue(entry);
state.pushActiveContextObject(kvpair);
@@ -80,7 +84,7 @@ public class Selection extends SpelNodeImpl {
if (((Boolean) o).booleanValue() == true) {
if (variant == FIRST) {
result.put(entry.getKey(),entry.getValue());
return new TypedValue(result);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
}
result.put(entry.getKey(),entry.getValue());
lastKey = entry.getKey();
@@ -94,15 +98,15 @@ public class Selection extends SpelNodeImpl {
}
}
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
return new TypedValue(null);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(null),this);
}
if (variant == LAST) {
Map resultMap = new HashMap();
Map<Object,Object> resultMap = new HashMap<Object,Object>();
Object lastValue = result.get(lastKey);
resultMap.put(lastKey,lastValue);
return new TypedValue(resultMap);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultMap),this);
}
return new TypedValue(result);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
} else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
List<Object> data = new ArrayList<Object>();
Collection<?> c = (operand instanceof Collection) ?
@@ -118,7 +122,7 @@ public class Selection extends SpelNodeImpl {
if (o instanceof Boolean) {
if (((Boolean) o).booleanValue() == true) {
if (variant == FIRST) {
return new TypedValue(element);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(element),this);
}
result.add(element);
}
@@ -133,24 +137,24 @@ public class Selection extends SpelNodeImpl {
}
}
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
return TypedValue.NULL;
return ValueRef.NullValueRef.instance;
}
if (variant == LAST) {
return new TypedValue(result.get(result.size() - 1));
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this);
}
if (operand instanceof Collection) {
return new TypedValue(result);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
}
else {
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType());
Object resultArray = Array.newInstance(elementType, result.size());
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
return new TypedValue(resultArray);
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
}
} else {
if (operand==null) {
if (nullSafe) {
return TypedValue.NULL;
return ValueRef.NullValueRef.instance;
} else {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
"null");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@@ -154,6 +154,10 @@ public abstract class SpelNodeImpl implements SpelNode {
public int getEndPosition() {
return (pos&0xffff);
}
}
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
throw new SpelEvaluationException(pos,SpelMessage.NOT_ASSIGNABLE,toStringAST());
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2002-2012 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.ast;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* Represents a reference to a value. With a reference it is possible to get or set the
* value. Passing around value references rather than the values themselves can avoid
* incorrect duplication of operand evaluation. For example in 'list[index++]++' without a
* value reference for 'list[index++]' it would be necessary to evaluate list[index++]
* twice (once to get the value, once to determine where the value goes) and that would
* double increment index.
*
* @author Andy Clement
* @since 3.2
*/
public interface ValueRef {
/**
* A ValueRef for the null value.
*/
static class NullValueRef implements ValueRef {
static NullValueRef instance = new NullValueRef();
public TypedValue getValue() {
return TypedValue.NULL;
}
public void setValue(Object newValue) {
// The exception position '0' isn't right but the overhead of creating
// instances of this per node (where the node is solely for error reporting)
// would be unfortunate.
throw new SpelEvaluationException(0,SpelMessage.NOT_ASSIGNABLE,"null");
}
public boolean isWritable() {
return false;
}
}
/**
* A ValueRef holder for a single value, which cannot be set.
*/
static class TypedValueHolderValueRef implements ValueRef {
private TypedValue typedValue;
private SpelNodeImpl node; // used only for error reporting
public TypedValueHolderValueRef(TypedValue typedValue,SpelNodeImpl node) {
this.typedValue = typedValue;
this.node = node;
}
public TypedValue getValue() {
return typedValue;
}
public void setValue(Object newValue) {
throw new SpelEvaluationException(
node.pos, SpelMessage.NOT_ASSIGNABLE, node.toStringAST());
}
public boolean isWritable() {
return false;
}
}
/**
* Returns the value this ValueRef points to, it should not require expression
* component re-evaluation.
* @return the value
*/
TypedValue getValue();
/**
* Sets the value this ValueRef points to, it should not require expression component
* re-evaluation.
* @param newValue the new value
*/
void setValue(Object newValue);
/**
* Indicates whether calling setValue(Object) is supported.
* @return true if setValue() is supported for this value reference.
*/
boolean isWritable();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@@ -16,6 +16,7 @@
package org.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
@@ -41,6 +42,47 @@ public class VariableReference extends SpelNodeImpl {
}
class VariableRef implements ValueRef {
private String name;
private TypedValue value;
private EvaluationContext eContext;
public VariableRef(String name, TypedValue value,
EvaluationContext evaluationContext) {
this.name = name;
this.value = value;
this.eContext = evaluationContext;
}
public TypedValue getValue() {
return value;
}
public void setValue(Object newValue) {
eContext.setVariable(name, newValue);
}
public boolean isWritable() {
return true;
}
}
@Override
public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException {
if (this.name.equals(THIS)) {
return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(),this);
}
if (this.name.equals(ROOT)) {
return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(),this);
}
TypedValue result = state.lookupVariable(this.name);
// a null value will mean either the value was null or the variable was not found
return new VariableRef(this.name,result,state.getEvaluationContext());
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
if (this.name.equals(THIS)) {

View File

@@ -27,45 +27,7 @@ import org.springframework.expression.spel.InternalParseException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.Assign;
import org.springframework.expression.spel.ast.BeanReference;
import org.springframework.expression.spel.ast.BooleanLiteral;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.ConstructorReference;
import org.springframework.expression.spel.ast.Elvis;
import org.springframework.expression.spel.ast.FunctionReference;
import org.springframework.expression.spel.ast.Identifier;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.Literal;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.expression.spel.ast.OpDivide;
import org.springframework.expression.spel.ast.OpEQ;
import org.springframework.expression.spel.ast.OpGE;
import org.springframework.expression.spel.ast.OpGT;
import org.springframework.expression.spel.ast.OpLE;
import org.springframework.expression.spel.ast.OpLT;
import org.springframework.expression.spel.ast.OpMinus;
import org.springframework.expression.spel.ast.OpModulus;
import org.springframework.expression.spel.ast.OpMultiply;
import org.springframework.expression.spel.ast.OpNE;
import org.springframework.expression.spel.ast.OpOr;
import org.springframework.expression.spel.ast.OpPlus;
import org.springframework.expression.spel.ast.OperatorInstanceof;
import org.springframework.expression.spel.ast.OperatorMatches;
import org.springframework.expression.spel.ast.OperatorNot;
import org.springframework.expression.spel.ast.OperatorPower;
import org.springframework.expression.spel.ast.Projection;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.ast.QualifiedIdentifier;
import org.springframework.expression.spel.ast.Selection;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.expression.spel.ast.Ternary;
import org.springframework.expression.spel.ast.TypeReference;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.expression.spel.ast.*;
import org.springframework.util.Assert;
/**
@@ -231,14 +193,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
private SpelNodeImpl eatSumExpression() {
SpelNodeImpl expr = eatProductExpression();
while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) {
Token t = nextToken();//consume PLUS or MINUS
while (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.INC)) {
Token t = nextToken();//consume PLUS or MINUS or INC
SpelNodeImpl rhExpr = eatProductExpression();
checkRightOperand(t,rhExpr);
if (t.kind==TokenKind.PLUS) {
expr = new OpPlus(toPos(t),expr,rhExpr);
} else {
Assert.isTrue(t.kind==TokenKind.MINUS);
} else if (t.kind==TokenKind.MINUS) {
expr = new OpMinus(toPos(t),expr,rhExpr);
}
}
@@ -247,10 +208,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
private SpelNodeImpl eatProductExpression() {
SpelNodeImpl expr = eatPowerExpression();
SpelNodeImpl expr = eatPowerIncDecExpression();
while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
Token t = nextToken(); // consume STAR/DIV/MOD
SpelNodeImpl rhExpr = eatPowerExpression();
SpelNodeImpl rhExpr = eatPowerIncDecExpression();
checkRightOperand(t,rhExpr);
if (t.kind==TokenKind.STAR) {
expr = new OpMultiply(toPos(t),expr,rhExpr);
@@ -264,19 +225,26 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return expr;
}
// powerExpr : unaryExpression (POWER^ unaryExpression)? ;
private SpelNodeImpl eatPowerExpression() {
// powerExpr : unaryExpression (POWER^ unaryExpression)? (INC || DEC) ;
private SpelNodeImpl eatPowerIncDecExpression() {
SpelNodeImpl expr = eatUnaryExpression();
if (peekToken(TokenKind.POWER)) {
Token t = nextToken();//consume POWER
SpelNodeImpl rhExpr = eatUnaryExpression();
checkRightOperand(t,rhExpr);
return new OperatorPower(toPos(t),expr, rhExpr);
} else if (expr!=null && peekToken(TokenKind.INC,TokenKind.DEC)) {
Token t = nextToken();//consume INC/DEC
if (t.getKind()==TokenKind.INC) {
return new OpInc(toPos(t),true,expr);
} else {
return new OpDec(toPos(t),true,expr);
}
}
return expr;
}
// unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ;
// unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ;
private SpelNodeImpl eatUnaryExpression() {
if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
Token t = nextToken();
@@ -289,6 +257,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
Assert.isTrue(t.kind==TokenKind.MINUS);
return new OpMinus(toPos(t),expr);
}
} else if (peekToken(TokenKind.INC,TokenKind.DEC)) {
Token t = nextToken();
SpelNodeImpl expr = eatUnaryExpression();
if (t.getKind()==TokenKind.INC) {
return new OpInc(toPos(t),false,expr);
} else {
return new OpDec(toPos(t),false,expr);
}
} else {
return eatPrimaryExpression();
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.standard;
/**
@@ -29,12 +30,12 @@ enum TokenKind {
DIV("/"), GE(">="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="),
MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"),
SELECT("?["), POWER("^"),
ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&")
ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&"), INC("++"), DEC("--")
;
char[] tokenChars;
private boolean hasPayload; // is there more to this token than simply the kind
private TokenKind(String tokenString) {
tokenChars = tokenString.toCharArray();
hasPayload = tokenChars.length==0;
@@ -43,15 +44,15 @@ enum TokenKind {
private TokenKind() {
this("");
}
public String toString() {
return this.name()+(tokenChars.length!=0?"("+new String(tokenChars)+")":"");
}
public boolean hasPayload() {
return hasPayload;
}
public int getLength() {
return tokenChars.length;
}

View File

@@ -55,13 +55,21 @@ class Tokenizer {
} else {
switch (ch) {
case '+':
pushCharToken(TokenKind.PLUS);
if (isTwoCharToken(TokenKind.INC)) {
pushPairToken(TokenKind.INC);
} else {
pushCharToken(TokenKind.PLUS);
}
break;
case '_': // the other way to start an identifier
lexIdentifier();
break;
case '-':
pushCharToken(TokenKind.MINUS);
if (isTwoCharToken(TokenKind.DEC)) {
pushPairToken(TokenKind.DEC);
} else {
pushCharToken(TokenKind.MINUS);
}
break;
case ':':
pushCharToken(TokenKind.COLON);