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:
parent
33d37e8680
commit
f64325882d
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -28,6 +28,7 @@ import org.springframework.expression.Operation;
|
||||||
import org.springframework.expression.OperatorOverloader;
|
import org.springframework.expression.OperatorOverloader;
|
||||||
import org.springframework.expression.PropertyAccessor;
|
import org.springframework.expression.PropertyAccessor;
|
||||||
import org.springframework.expression.TypeComparator;
|
import org.springframework.expression.TypeComparator;
|
||||||
|
import org.springframework.expression.TypeConverter;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,6 +141,10 @@ public class ExpressionState {
|
||||||
return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor);
|
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 {
|
public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
|
||||||
Object val = value.getValue();
|
Object val = value.getValue();
|
||||||
return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor);
|
return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor);
|
||||||
|
|
|
@ -104,7 +104,10 @@ public enum SpelMessage {
|
||||||
MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), //
|
MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), //
|
||||||
INITIALIZER_LENGTH_INCORRECT(
|
INITIALIZER_LENGTH_INCORRECT(
|
||||||
Kind.ERROR, 1064, "array initializer size does not match array dimensions"), //
|
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;
|
private Kind kind;
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -41,10 +41,10 @@ public class AstUtils {
|
||||||
* @param targetType the type upon which property access is being attempted
|
* @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
|
* @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> specificAccessors = new ArrayList<PropertyAccessor>();
|
||||||
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
|
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
|
||||||
for (PropertyAccessor resolver : state.getPropertyAccessors()) {
|
for (PropertyAccessor resolver : propertyAccessors) {
|
||||||
Class<?>[] targets = resolver.getSpecificTargetClasses();
|
Class<?>[] targets = resolver.getSpecificTargetClasses();
|
||||||
if (targets == null) { // generic resolver that says it can be used for any type
|
if (targets == null) { // generic resolver that says it can be used for any type
|
||||||
generalAccessors.add(resolver);
|
generalAccessors.add(resolver);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -35,22 +35,19 @@ public class CompoundExpression extends SpelNodeImpl {
|
||||||
throw new IllegalStateException("Dont build compound expression less than one entry: "+expressionComponents.length);
|
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
|
@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;
|
TypedValue result = null;
|
||||||
SpelNodeImpl nextNode = null;
|
SpelNodeImpl nextNode = null;
|
||||||
try {
|
try {
|
||||||
nextNode = children[0];
|
nextNode = children[0];
|
||||||
result = nextNode.getValueInternal(state);
|
result = nextNode.getValueInternal(state);
|
||||||
for (int i = 1; i < getChildCount(); i++) {
|
int cc = getChildCount();
|
||||||
|
for (int i = 1; i < cc-1; i++) {
|
||||||
try {
|
try {
|
||||||
state.pushActiveContextObject(result);
|
state.pushActiveContextObject(result);
|
||||||
nextNode = children[i];
|
nextNode = children[i];
|
||||||
|
@ -59,57 +56,39 @@ public class CompoundExpression extends SpelNodeImpl {
|
||||||
state.popActiveContextObject();
|
state.popActiveContextObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
state.pushActiveContextObject(result);
|
||||||
|
nextNode = children[cc-1];
|
||||||
|
return nextNode.getValueRef(state);
|
||||||
|
} finally {
|
||||||
|
state.popActiveContextObject();
|
||||||
|
}
|
||||||
} catch (SpelEvaluationException ee) {
|
} catch (SpelEvaluationException ee) {
|
||||||
// Correct the position for the error before rethrowing
|
// Correct the position for the error before re-throwing
|
||||||
ee.setPosition(nextNode.getStartPosition());
|
ee.setPosition(nextNode.getStartPosition());
|
||||||
throw ee;
|
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
|
@Override
|
||||||
public void setValue(ExpressionState state, Object value) throws EvaluationException {
|
public void setValue(ExpressionState state, Object value) throws EvaluationException {
|
||||||
if (getChildCount() == 1) {
|
getValueRef(state).setValue(value);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWritable(ExpressionState state) throws EvaluationException {
|
public boolean isWritable(ExpressionState state) throws EvaluationException {
|
||||||
if (getChildCount() == 1) {
|
return getValueRef(state).isWritable();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -25,6 +25,7 @@ import org.springframework.expression.AccessException;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.PropertyAccessor;
|
import org.springframework.expression.PropertyAccessor;
|
||||||
|
import org.springframework.expression.TypeConverter;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
import org.springframework.expression.spel.ExpressionState;
|
import org.springframework.expression.spel.ExpressionState;
|
||||||
import org.springframework.expression.spel.SpelEvaluationException;
|
import org.springframework.expression.spel.SpelEvaluationException;
|
||||||
|
@ -32,8 +33,9 @@ import org.springframework.expression.spel.SpelMessage;
|
||||||
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
|
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Indexer can index into some proceeding structure to access a particular piece of it.
|
* An Indexer can index into some proceeding structure to access a particular
|
||||||
* Supported structures are: strings/collections (lists/sets)/arrays
|
* piece of it. Supported structures are: strings/collections
|
||||||
|
* (lists/sets)/arrays
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
|
@ -42,20 +44,21 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
|
||||||
// TODO support correct syntax for multidimensional [][][] and not [,,,]
|
// TODO support correct syntax for multidimensional [][][] and not [,,,]
|
||||||
public class Indexer extends SpelNodeImpl {
|
public class Indexer extends SpelNodeImpl {
|
||||||
|
|
||||||
// These fields are used when the indexer is being used as a property read accessor. If the name and
|
// These fields are used when the indexer is being used as a property read accessor.
|
||||||
// target type match these cached values then the cachedReadAccessor is used to read the property.
|
// If the name and target type match these cached values then the cachedReadAccessor
|
||||||
// If they do not match, the correct accessor is discovered and then cached for later use.
|
// 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 String cachedReadName;
|
||||||
private Class<?> cachedReadTargetType;
|
private Class<?> cachedReadTargetType;
|
||||||
private PropertyAccessor cachedReadAccessor;
|
private PropertyAccessor cachedReadAccessor;
|
||||||
|
|
||||||
// These fields are used when the indexer is being used as a property write accessor. If the name and
|
// These fields are used when the indexer is being used as a property write accessor.
|
||||||
// target type match these cached values then the cachedWriteAccessor is used to write the property.
|
// If the name and target type match these cached values then the cachedWriteAccessor
|
||||||
// If they do not match, the correct accessor is discovered and then cached for later use.
|
// 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 String cachedWriteName;
|
||||||
private Class<?> cachedWriteTargetType;
|
private Class<?> cachedWriteTargetType;
|
||||||
private PropertyAccessor cachedWriteAccessor;
|
private PropertyAccessor cachedWriteAccessor;
|
||||||
|
|
||||||
|
|
||||||
public Indexer(int pos, SpelNodeImpl expr) {
|
public Indexer(int pos, SpelNodeImpl expr) {
|
||||||
super(pos, expr);
|
super(pos, expr);
|
||||||
|
@ -63,27 +66,313 @@ public class Indexer extends SpelNodeImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
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();
|
TypedValue context = state.getActiveContextObject();
|
||||||
Object targetObject = context.getValue();
|
Object targetObject = context.getValue();
|
||||||
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor();
|
TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor();
|
||||||
TypedValue indexValue = null;
|
TypedValue indexValue = null;
|
||||||
Object index = 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)) {
|
if (targetObject instanceof Map && (children[0] instanceof PropertyOrFieldReference)) {
|
||||||
PropertyOrFieldReference reference = (PropertyOrFieldReference)children[0];
|
PropertyOrFieldReference reference = (PropertyOrFieldReference) children[0];
|
||||||
index = reference.getName();
|
index = reference.getName();
|
||||||
indexValue = new TypedValue(index);
|
indexValue = new TypedValue(index);
|
||||||
}
|
} else {
|
||||||
else {
|
// In case the map key is unqualified, we want it evaluated against
|
||||||
// In case the map key is unqualified, we want it evaluated against the root object so
|
// the root object so temporarily push that on whilst evaluating the key
|
||||||
// temporarily push that on whilst evaluating the key
|
|
||||||
try {
|
try {
|
||||||
state.pushActiveContextObject(state.getRootContextObject());
|
state.pushActiveContextObject(state.getRootContextObject());
|
||||||
indexValue = children[0].getValueInternal(state);
|
indexValue = children[0].getValueInternal(state);
|
||||||
index = indexValue.getValue();
|
index = indexValue.getValue();
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
state.popActiveContextObject();
|
state.popActiveContextObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,207 +381,83 @@ public class Indexer extends SpelNodeImpl {
|
||||||
if (targetObject instanceof Map) {
|
if (targetObject instanceof Map) {
|
||||||
Object key = index;
|
Object key = index;
|
||||||
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
|
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
|
||||||
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
key = state.convertValue(key,
|
||||||
|
targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
||||||
}
|
}
|
||||||
Object value = ((Map<?, ?>) targetObject).get(key);
|
return new MapIndexingValueRef(state.getTypeConverter(),
|
||||||
return new TypedValue(value, targetObjectTypeDescriptor.getMapValueTypeDescriptor(value));
|
(Map<?, ?>) targetObject, key, targetObjectTypeDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetObject == null) {
|
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 the object is something that looks indexable by an integer,
|
||||||
if (targetObject instanceof Collection || targetObject.getClass().isArray() || targetObject instanceof String) {
|
// attempt to treat the index value as a number
|
||||||
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
if (targetObject instanceof Collection
|
||||||
|
|| targetObject.getClass().isArray()
|
||||||
|
|| targetObject instanceof String) {
|
||||||
|
int idx = (Integer) state.convertValue(index,
|
||||||
|
TypeDescriptor.valueOf(Integer.class));
|
||||||
if (targetObject.getClass().isArray()) {
|
if (targetObject.getClass().isArray()) {
|
||||||
Object arrayElement = accessArrayElement(targetObject, idx);
|
return new ArrayIndexingValueRef(state.getTypeConverter(),
|
||||||
return new TypedValue(arrayElement, targetObjectTypeDescriptor.elementTypeDescriptor(arrayElement));
|
targetObject, idx, targetObjectTypeDescriptor);
|
||||||
} else if (targetObject instanceof Collection) {
|
} else if (targetObject instanceof Collection) {
|
||||||
Collection c = (Collection) targetObject;
|
return new CollectionIndexingValueRef(
|
||||||
if (idx >= c.size()) {
|
(Collection<?>) targetObject, idx,
|
||||||
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
|
targetObjectTypeDescriptor,state.getTypeConverter(),
|
||||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
state.getConfiguration().isAutoGrowCollections());
|
||||||
}
|
|
||||||
}
|
|
||||||
int pos = 0;
|
|
||||||
for (Object o : c) {
|
|
||||||
if (pos == idx) {
|
|
||||||
return new TypedValue(o, targetObjectTypeDescriptor.elementTypeDescriptor(o));
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
} else if (targetObject instanceof String) {
|
} else if (targetObject instanceof String) {
|
||||||
String ctxString = (String) targetObject;
|
return new StringIndexingLValue((String) targetObject, idx,
|
||||||
if (idx >= ctxString.length()) {
|
targetObjectTypeDescriptor);
|
||||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, ctxString.length(), idx);
|
|
||||||
}
|
|
||||||
return new TypedValue(String.valueOf(ctxString.charAt(idx)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and treat the index value as a property of the context object
|
// Try and treat the index value as a property of the context object
|
||||||
// TODO could call the conversion service to convert the value to a String
|
// TODO could call the conversion service to convert the value to a
|
||||||
if (indexValue.getTypeDescriptor().getType()==String.class) {
|
// String
|
||||||
Class<?> targetObjectRuntimeClass = getObjectClass(targetObject);
|
if (indexValue.getTypeDescriptor().getType() == String.class) {
|
||||||
String name = (String)indexValue.getValue();
|
return new PropertyIndexingValueRef(targetObject,
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
(String) indexValue.getValue(),
|
||||||
|
state.getEvaluationContext(), targetObjectTypeDescriptor);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
* Attempt to grow the specified collection so that the specified index is
|
||||||
*
|
* valid.
|
||||||
* @param state the expression state
|
*
|
||||||
* @param elementType the type of the elements in the collection
|
* @param elementType the type of the elements in the collection
|
||||||
* @param index the index into the collection that needs to be valid
|
* @param index the index into the collection that needs to be valid
|
||||||
* @param collection the collection to grow with elements
|
* @param collection the collection to grow with elements
|
||||||
* @return true if collection growing succeeded, otherwise false
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
private void growCollection(TypeDescriptor targetType, int index, Collection<Object> collection) {
|
||||||
private boolean growCollection(ExpressionState state, TypeDescriptor targetType, int index,
|
if (targetType.getElementTypeDescriptor() == null) {
|
||||||
Collection collection) {
|
throw new SpelEvaluationException(
|
||||||
if (state.getConfiguration().isAutoGrowCollections()) {
|
getStartPosition(),
|
||||||
if (targetType.getElementTypeDescriptor() == null) {
|
SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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
|
@Override
|
||||||
public String toStringAST() {
|
public String toStringAST() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
@ -306,47 +471,66 @@ public class Indexer extends SpelNodeImpl {
|
||||||
return sb.toString();
|
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;
|
Class<?> arrayComponentType = clazz;
|
||||||
if (arrayComponentType == Integer.TYPE) {
|
if (arrayComponentType == Integer.TYPE) {
|
||||||
int[] array = (int[]) ctx;
|
int[] array = (int[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
checkAccess(array.length, idx);
|
||||||
array[idx] = (Integer)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) {
|
} else if (arrayComponentType == Boolean.TYPE) {
|
||||||
boolean[] array = (boolean[]) ctx;
|
boolean[] array = (boolean[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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) {
|
} else if (arrayComponentType == Character.TYPE) {
|
||||||
char[] array = (char[]) ctx;
|
char[] array = (char[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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) {
|
} else if (arrayComponentType == Long.TYPE) {
|
||||||
long[] array = (long[]) ctx;
|
long[] array = (long[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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) {
|
} else if (arrayComponentType == Short.TYPE) {
|
||||||
short[] array = (short[]) ctx;
|
short[] array = (short[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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) {
|
} else if (arrayComponentType == Double.TYPE) {
|
||||||
double[] array = (double[]) ctx;
|
double[] array = (double[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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) {
|
} else if (arrayComponentType == Float.TYPE) {
|
||||||
float[] array = (float[]) ctx;
|
float[] array = (float[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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) {
|
} else if (arrayComponentType == Byte.TYPE) {
|
||||||
byte[] array = (byte[]) ctx;
|
byte[] array = (byte[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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 {
|
} else {
|
||||||
Object[] array = (Object[]) ctx;
|
Object[] array = (Object[]) ctx;
|
||||||
checkAccess(array.length, idx);
|
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 {
|
private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException {
|
||||||
Class<?> arrayComponentType = ctx.getClass().getComponentType();
|
Class<?> arrayComponentType = ctx.getClass().getComponentType();
|
||||||
if (arrayComponentType == Integer.TYPE) {
|
if (arrayComponentType == Integer.TYPE) {
|
||||||
|
@ -390,8 +574,9 @@ public class Indexer extends SpelNodeImpl {
|
||||||
|
|
||||||
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
|
private void checkAccess(int arrayLength, int index) throws SpelEvaluationException {
|
||||||
if (index > arrayLength) {
|
if (index > arrayLength) {
|
||||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index);
|
throw new SpelEvaluationException(getStartPosition(),
|
||||||
|
SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -52,6 +52,94 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
this.nullSafe = nullSafe;
|
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
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
|
@ -65,7 +153,7 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
arguments[i] = children[i].getValueInternal(state).getValue();
|
arguments[i] = children[i].getValueInternal(state).getValue();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
state.popActiveContextObject();
|
state.popActiveContextObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentContext.getValue() == null) {
|
if (currentContext.getValue() == null) {
|
||||||
|
@ -88,15 +176,15 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
// Two reasons this can occur:
|
// Two reasons this can occur:
|
||||||
// 1. the method invoked actually threw a real exception
|
// 1. the method invoked actually threw a real exception
|
||||||
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
|
// 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.
|
// better suited method.
|
||||||
|
|
||||||
// To determine which situation it is, the AccessException will contain a cause.
|
// 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.
|
// If the cause is an InvocationTargetException, a user exception was thrown inside the method.
|
||||||
// Otherwise the method could not be invoked.
|
// Otherwise the method could not be invoked.
|
||||||
throwSimpleExceptionIfPossible(state, ae);
|
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
|
// 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;
|
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.
|
* throw the RuntimeException directly.
|
||||||
*/
|
*/
|
||||||
private void throwSimpleExceptionIfPossible(ExpressionState state, AccessException ae) {
|
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 +
|
"A problem occurred when trying to execute method '" + this.name +
|
||||||
"' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'",
|
"' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'",
|
||||||
rootCause);
|
rootCause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,19 +245,22 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
return sb.toString();
|
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 {
|
throws SpelEvaluationException {
|
||||||
|
return findAccessorForMethod(name,argumentTypes,state.getActiveContextObject().getValue(),state.getEvaluationContext());
|
||||||
|
}
|
||||||
|
|
||||||
TypedValue context = state.getActiveContextObject();
|
private MethodExecutor findAccessorForMethod(String name,
|
||||||
Object contextObject = context.getValue();
|
List<TypeDescriptor> argumentTypes, Object contextObject, EvaluationContext eContext)
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
throws SpelEvaluationException {
|
||||||
|
|
||||||
List<MethodResolver> mResolvers = eContext.getMethodResolvers();
|
List<MethodResolver> mResolvers = eContext.getMethodResolvers();
|
||||||
if (mResolvers != null) {
|
if (mResolvers != null) {
|
||||||
for (MethodResolver methodResolver : mResolvers) {
|
for (MethodResolver methodResolver : mResolvers) {
|
||||||
try {
|
try {
|
||||||
MethodExecutor cEx = methodResolver.resolve(
|
MethodExecutor cEx = methodResolver.resolve(
|
||||||
state.getEvaluationContext(), contextObject, name, argumentTypes);
|
eContext, contextObject, name, argumentTypes);
|
||||||
if (cEx != null) {
|
if (cEx != null) {
|
||||||
return cEx;
|
return cEx;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -51,6 +51,11 @@ public class Projection extends SpelNodeImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
|
return getValueRef(state).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||||
TypedValue op = state.getActiveContextObject();
|
TypedValue op = state.getActiveContextObject();
|
||||||
|
|
||||||
Object operand = op.getValue();
|
Object operand = op.getValue();
|
||||||
|
@ -65,7 +70,7 @@ public class Projection extends SpelNodeImpl {
|
||||||
if (operand instanceof Map) {
|
if (operand instanceof Map) {
|
||||||
Map<?, ?> mapData = (Map<?, ?>) operand;
|
Map<?, ?> mapData = (Map<?, ?>) operand;
|
||||||
List<Object> result = new ArrayList<Object>();
|
List<Object> result = new ArrayList<Object>();
|
||||||
for (Map.Entry entry : mapData.entrySet()) {
|
for (Map.Entry<?,?> entry : mapData.entrySet()) {
|
||||||
try {
|
try {
|
||||||
state.pushActiveContextObject(new TypedValue(entry));
|
state.pushActiveContextObject(new TypedValue(entry));
|
||||||
result.add(this.children[0].getValueInternal(state).getValue());
|
result.add(this.children[0].getValueInternal(state).getValue());
|
||||||
|
@ -74,7 +79,7 @@ public class Projection extends SpelNodeImpl {
|
||||||
state.popActiveContextObject();
|
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) {
|
else if (operand instanceof Collection || operandIsArray) {
|
||||||
Collection<?> data = (operand instanceof Collection ? (Collection<?>) operand :
|
Collection<?> data = (operand instanceof Collection ? (Collection<?>) operand :
|
||||||
|
@ -104,14 +109,14 @@ public class Projection extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
Object resultArray = Array.newInstance(arrayElementType, result.size());
|
Object resultArray = Array.newInstance(arrayElementType, result.size());
|
||||||
System.arraycopy(result.toArray(), 0, resultArray, 0, 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 {
|
else {
|
||||||
if (operand==null) {
|
if (operand==null) {
|
||||||
if (this.nullSafe) {
|
if (this.nullSafe) {
|
||||||
return TypedValue.NULL;
|
return ValueRef.NullValueRef.instance;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SpelEvaluationException(getStartPosition(),
|
throw new SpelEvaluationException(getStartPosition(),
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -49,7 +49,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
private volatile PropertyAccessor cachedReadAccessor;
|
private volatile PropertyAccessor cachedReadAccessor;
|
||||||
|
|
||||||
private volatile PropertyAccessor cachedWriteAccessor;
|
private volatile PropertyAccessor cachedWriteAccessor;
|
||||||
|
|
||||||
|
|
||||||
public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) {
|
public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) {
|
||||||
super(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
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
TypedValue result = readProperty(state, this.name);
|
return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences());
|
||||||
|
}
|
||||||
// Dynamically create the objects if the user has requested that optional behaviour
|
|
||||||
if (result.getValue() == null && 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)) {
|
nextChildIs(Indexer.class, PropertyOrFieldReference.class)) {
|
||||||
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
|
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
|
||||||
// Creating lists and maps
|
// Creating lists and maps
|
||||||
|
@ -80,10 +120,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
// Create a new collection or map ready for the indexer
|
// Create a new collection or map ready for the indexer
|
||||||
if (resultDescriptor.getType().equals(List.class)) {
|
if (resultDescriptor.getType().equals(List.class)) {
|
||||||
try {
|
try {
|
||||||
if (isWritable(state)) {
|
if (isWritableProperty(this.name,contextObject,eContext)) {
|
||||||
List newList = ArrayList.class.newInstance();
|
List<?> newList = ArrayList.class.newInstance();
|
||||||
writeProperty(state, this.name, newList);
|
writeProperty(contextObject, eContext, this.name, newList);
|
||||||
result = readProperty(state, this.name);
|
result = readProperty(contextObject, eContext, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InstantiationException ex) {
|
catch (InstantiationException ex) {
|
||||||
|
@ -97,10 +137,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
if (isWritable(state)) {
|
if (isWritableProperty(this.name,contextObject,eContext)) {
|
||||||
Map newMap = HashMap.class.newInstance();
|
Map<?,?> newMap = HashMap.class.newInstance();
|
||||||
writeProperty(state, name, newMap);
|
writeProperty(contextObject, eContext, name, newMap);
|
||||||
result = readProperty(state, this.name);
|
result = readProperty(contextObject, eContext, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InstantiationException ex) {
|
catch (InstantiationException ex) {
|
||||||
|
@ -116,10 +156,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
else {
|
else {
|
||||||
// 'simple' object
|
// 'simple' object
|
||||||
try {
|
try {
|
||||||
if (isWritable(state)) {
|
if (isWritableProperty(this.name,contextObject,eContext)) {
|
||||||
Object newObject = result.getTypeDescriptor().getType().newInstance();
|
Object newObject = result.getTypeDescriptor().getType().newInstance();
|
||||||
writeProperty(state, name, newObject);
|
writeProperty(contextObject, eContext, name, newObject);
|
||||||
result = readProperty(state, this.name);
|
result = readProperty(contextObject, eContext, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InstantiationException ex) {
|
catch (InstantiationException ex) {
|
||||||
|
@ -137,12 +177,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setValue(ExpressionState state, Object newValue) throws SpelEvaluationException {
|
public void setValue(ExpressionState state, Object newValue) throws SpelEvaluationException {
|
||||||
writeProperty(state, this.name, newValue);
|
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWritable(ExpressionState state) throws SpelEvaluationException {
|
public boolean isWritable(ExpressionState state) throws SpelEvaluationException {
|
||||||
return isWritableProperty(this.name, state);
|
return isWritableProperty(this.name, state.getActiveContextObject(), state.getEvaluationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -157,8 +197,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
* @return the value of the property
|
* @return the value of the property
|
||||||
* @throws SpelEvaluationException if any problem accessing the property or it cannot be found
|
* @throws SpelEvaluationException if any problem accessing the property or it cannot be found
|
||||||
*/
|
*/
|
||||||
private TypedValue readProperty(ExpressionState state, String name) throws EvaluationException {
|
private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException {
|
||||||
TypedValue contextObject = state.getActiveContextObject();
|
|
||||||
Object targetObject = contextObject.getValue();
|
Object targetObject = contextObject.getValue();
|
||||||
|
|
||||||
if (targetObject == null && this.nullSafe) {
|
if (targetObject == null && this.nullSafe) {
|
||||||
|
@ -168,7 +207,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
PropertyAccessor accessorToUse = this.cachedReadAccessor;
|
PropertyAccessor accessorToUse = this.cachedReadAccessor;
|
||||||
if (accessorToUse != null) {
|
if (accessorToUse != null) {
|
||||||
try {
|
try {
|
||||||
return accessorToUse.read(state.getEvaluationContext(), contextObject.getValue(), name);
|
return accessorToUse.read(eContext, contextObject.getValue(), name);
|
||||||
}
|
}
|
||||||
catch (AccessException ae) {
|
catch (AccessException ae) {
|
||||||
// this is OK - it may have gone stale due to a class change,
|
// 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());
|
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
||||||
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
|
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors());
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
|
||||||
|
|
||||||
// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
|
// 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
|
// 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 {
|
private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException {
|
||||||
TypedValue contextObject = state.getActiveContextObject();
|
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
|
||||||
|
|
||||||
if (contextObject.getValue() == null && nullSafe) {
|
if (contextObject.getValue() == null && nullSafe) {
|
||||||
return;
|
return;
|
||||||
|
@ -220,8 +256,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
PropertyAccessor accessorToUse = this.cachedWriteAccessor;
|
PropertyAccessor accessorToUse = this.cachedWriteAccessor;
|
||||||
if (accessorToUse != null) {
|
if (accessorToUse != null) {
|
||||||
try {
|
try {
|
||||||
accessorToUse.write(state.getEvaluationContext(), contextObject.getValue(), name, newValue);
|
accessorToUse.write(eContext, contextObject.getValue(), name, newValue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (AccessException ae) {
|
catch (AccessException ae) {
|
||||||
|
@ -233,7 +269,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
||||||
|
|
||||||
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state);
|
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors());
|
||||||
if (accessorsToTry != null) {
|
if (accessorsToTry != null) {
|
||||||
try {
|
try {
|
||||||
for (PropertyAccessor accessor : accessorsToTry) {
|
for (PropertyAccessor accessor : accessorsToTry) {
|
||||||
|
@ -258,15 +294,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
|
public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext eContext) throws SpelEvaluationException {
|
||||||
Object contextObject = state.getActiveContextObject().getValue();
|
Object contextObjectValue = contextObject.getValue();
|
||||||
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObjectValue), eContext.getPropertyAccessors());
|
||||||
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject), state);
|
|
||||||
if (resolversToTry != null) {
|
if (resolversToTry != null) {
|
||||||
for (PropertyAccessor pfResolver : resolversToTry) {
|
for (PropertyAccessor pfResolver : resolversToTry) {
|
||||||
try {
|
try {
|
||||||
if (pfResolver.canWrite(eContext, contextObject, name)) {
|
if (pfResolver.canWrite(eContext, contextObjectValue, name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,10 +324,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
* @param targetType the type upon which property access is being attempted
|
* @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
|
* @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> specificAccessors = new ArrayList<PropertyAccessor>();
|
||||||
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
|
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
|
||||||
for (PropertyAccessor resolver : state.getPropertyAccessors()) {
|
for (PropertyAccessor resolver : propertyAccessors) {
|
||||||
Class<?>[] targets = resolver.getSpecificTargetClasses();
|
Class<?>[] targets = resolver.getSpecificTargetClasses();
|
||||||
if (targets == null) { // generic resolver that says it can be used for any type
|
if (targets == null) { // generic resolver that says it can be used for any type
|
||||||
generalAccessors.add(resolver);
|
generalAccessors.add(resolver);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -59,19 +59,23 @@ public class Selection extends SpelNodeImpl {
|
||||||
this.variant = variant;
|
this.variant = variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
|
return getValueRef(state).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||||
TypedValue op = state.getActiveContextObject();
|
TypedValue op = state.getActiveContextObject();
|
||||||
Object operand = op.getValue();
|
Object operand = op.getValue();
|
||||||
|
|
||||||
SpelNodeImpl selectionCriteria = children[0];
|
SpelNodeImpl selectionCriteria = children[0];
|
||||||
if (operand instanceof Map) {
|
if (operand instanceof Map) {
|
||||||
Map<?, ?> mapdata = (Map<?, ?>) operand;
|
Map<?, ?> mapdata = (Map<?, ?>) operand;
|
||||||
// TODO don't lose generic info for the new map
|
// TODO don't lose generic info for the new map
|
||||||
Map<Object,Object> result = new HashMap<Object,Object>();
|
Map<Object,Object> result = new HashMap<Object,Object>();
|
||||||
Object lastKey = null;
|
Object lastKey = null;
|
||||||
for (Map.Entry entry : mapdata.entrySet()) {
|
for (Map.Entry<?,?> entry : mapdata.entrySet()) {
|
||||||
try {
|
try {
|
||||||
TypedValue kvpair = new TypedValue(entry);
|
TypedValue kvpair = new TypedValue(entry);
|
||||||
state.pushActiveContextObject(kvpair);
|
state.pushActiveContextObject(kvpair);
|
||||||
|
@ -80,7 +84,7 @@ public class Selection extends SpelNodeImpl {
|
||||||
if (((Boolean) o).booleanValue() == true) {
|
if (((Boolean) o).booleanValue() == true) {
|
||||||
if (variant == FIRST) {
|
if (variant == FIRST) {
|
||||||
result.put(entry.getKey(),entry.getValue());
|
result.put(entry.getKey(),entry.getValue());
|
||||||
return new TypedValue(result);
|
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
|
||||||
}
|
}
|
||||||
result.put(entry.getKey(),entry.getValue());
|
result.put(entry.getKey(),entry.getValue());
|
||||||
lastKey = entry.getKey();
|
lastKey = entry.getKey();
|
||||||
|
@ -94,15 +98,15 @@ public class Selection extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
|
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
|
||||||
return new TypedValue(null);
|
return new ValueRef.TypedValueHolderValueRef(new TypedValue(null),this);
|
||||||
}
|
}
|
||||||
if (variant == LAST) {
|
if (variant == LAST) {
|
||||||
Map resultMap = new HashMap();
|
Map<Object,Object> resultMap = new HashMap<Object,Object>();
|
||||||
Object lastValue = result.get(lastKey);
|
Object lastValue = result.get(lastKey);
|
||||||
resultMap.put(lastKey,lastValue);
|
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)) {
|
} else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
|
||||||
List<Object> data = new ArrayList<Object>();
|
List<Object> data = new ArrayList<Object>();
|
||||||
Collection<?> c = (operand instanceof Collection) ?
|
Collection<?> c = (operand instanceof Collection) ?
|
||||||
|
@ -118,7 +122,7 @@ public class Selection extends SpelNodeImpl {
|
||||||
if (o instanceof Boolean) {
|
if (o instanceof Boolean) {
|
||||||
if (((Boolean) o).booleanValue() == true) {
|
if (((Boolean) o).booleanValue() == true) {
|
||||||
if (variant == FIRST) {
|
if (variant == FIRST) {
|
||||||
return new TypedValue(element);
|
return new ValueRef.TypedValueHolderValueRef(new TypedValue(element),this);
|
||||||
}
|
}
|
||||||
result.add(element);
|
result.add(element);
|
||||||
}
|
}
|
||||||
|
@ -133,24 +137,24 @@ public class Selection extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
|
if ((variant == FIRST || variant == LAST) && result.size() == 0) {
|
||||||
return TypedValue.NULL;
|
return ValueRef.NullValueRef.instance;
|
||||||
}
|
}
|
||||||
if (variant == LAST) {
|
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) {
|
if (operand instanceof Collection) {
|
||||||
return new TypedValue(result);
|
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType());
|
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType());
|
||||||
Object resultArray = Array.newInstance(elementType, result.size());
|
Object resultArray = Array.newInstance(elementType, result.size());
|
||||||
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
|
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
|
||||||
return new TypedValue(resultArray);
|
return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (operand==null) {
|
if (operand==null) {
|
||||||
if (nullSafe) {
|
if (nullSafe) {
|
||||||
return TypedValue.NULL;
|
return ValueRef.NullValueRef.instance;
|
||||||
} else {
|
} else {
|
||||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
|
||||||
"null");
|
"null");
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -154,6 +154,10 @@ public abstract class SpelNodeImpl implements SpelNode {
|
||||||
|
|
||||||
public int getEndPosition() {
|
public int getEndPosition() {
|
||||||
return (pos&0xffff);
|
return (pos&0xffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||||
|
throw new SpelEvaluationException(pos,SpelMessage.NOT_ASSIGNABLE,toStringAST());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel.ast;
|
package org.springframework.expression.spel.ast;
|
||||||
|
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
import org.springframework.expression.spel.ExpressionState;
|
import org.springframework.expression.spel.ExpressionState;
|
||||||
import org.springframework.expression.spel.SpelEvaluationException;
|
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
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
|
||||||
if (this.name.equals(THIS)) {
|
if (this.name.equals(THIS)) {
|
||||||
|
|
|
@ -27,45 +27,7 @@ import org.springframework.expression.spel.InternalParseException;
|
||||||
import org.springframework.expression.spel.SpelMessage;
|
import org.springframework.expression.spel.SpelMessage;
|
||||||
import org.springframework.expression.spel.SpelParseException;
|
import org.springframework.expression.spel.SpelParseException;
|
||||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||||
import org.springframework.expression.spel.ast.Assign;
|
import org.springframework.expression.spel.ast.*;
|
||||||
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.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -231,14 +193,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
|
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
|
||||||
private SpelNodeImpl eatSumExpression() {
|
private SpelNodeImpl eatSumExpression() {
|
||||||
SpelNodeImpl expr = eatProductExpression();
|
SpelNodeImpl expr = eatProductExpression();
|
||||||
while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) {
|
while (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.INC)) {
|
||||||
Token t = nextToken();//consume PLUS or MINUS
|
Token t = nextToken();//consume PLUS or MINUS or INC
|
||||||
SpelNodeImpl rhExpr = eatProductExpression();
|
SpelNodeImpl rhExpr = eatProductExpression();
|
||||||
checkRightOperand(t,rhExpr);
|
checkRightOperand(t,rhExpr);
|
||||||
if (t.kind==TokenKind.PLUS) {
|
if (t.kind==TokenKind.PLUS) {
|
||||||
expr = new OpPlus(toPos(t),expr,rhExpr);
|
expr = new OpPlus(toPos(t),expr,rhExpr);
|
||||||
} else {
|
} else if (t.kind==TokenKind.MINUS) {
|
||||||
Assert.isTrue(t.kind==TokenKind.MINUS);
|
|
||||||
expr = new OpMinus(toPos(t),expr,rhExpr);
|
expr = new OpMinus(toPos(t),expr,rhExpr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,10 +208,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
|
|
||||||
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
|
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
|
||||||
private SpelNodeImpl eatProductExpression() {
|
private SpelNodeImpl eatProductExpression() {
|
||||||
SpelNodeImpl expr = eatPowerExpression();
|
SpelNodeImpl expr = eatPowerIncDecExpression();
|
||||||
while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
|
while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
|
||||||
Token t = nextToken(); // consume STAR/DIV/MOD
|
Token t = nextToken(); // consume STAR/DIV/MOD
|
||||||
SpelNodeImpl rhExpr = eatPowerExpression();
|
SpelNodeImpl rhExpr = eatPowerIncDecExpression();
|
||||||
checkRightOperand(t,rhExpr);
|
checkRightOperand(t,rhExpr);
|
||||||
if (t.kind==TokenKind.STAR) {
|
if (t.kind==TokenKind.STAR) {
|
||||||
expr = new OpMultiply(toPos(t),expr,rhExpr);
|
expr = new OpMultiply(toPos(t),expr,rhExpr);
|
||||||
|
@ -264,19 +225,26 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// powerExpr : unaryExpression (POWER^ unaryExpression)? ;
|
// powerExpr : unaryExpression (POWER^ unaryExpression)? (INC || DEC) ;
|
||||||
private SpelNodeImpl eatPowerExpression() {
|
private SpelNodeImpl eatPowerIncDecExpression() {
|
||||||
SpelNodeImpl expr = eatUnaryExpression();
|
SpelNodeImpl expr = eatUnaryExpression();
|
||||||
if (peekToken(TokenKind.POWER)) {
|
if (peekToken(TokenKind.POWER)) {
|
||||||
Token t = nextToken();//consume POWER
|
Token t = nextToken();//consume POWER
|
||||||
SpelNodeImpl rhExpr = eatUnaryExpression();
|
SpelNodeImpl rhExpr = eatUnaryExpression();
|
||||||
checkRightOperand(t,rhExpr);
|
checkRightOperand(t,rhExpr);
|
||||||
return new OperatorPower(toPos(t),expr, 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;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ;
|
// unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ;
|
||||||
private SpelNodeImpl eatUnaryExpression() {
|
private SpelNodeImpl eatUnaryExpression() {
|
||||||
if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
|
if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
|
||||||
Token t = nextToken();
|
Token t = nextToken();
|
||||||
|
@ -289,6 +257,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
Assert.isTrue(t.kind==TokenKind.MINUS);
|
Assert.isTrue(t.kind==TokenKind.MINUS);
|
||||||
return new OpMinus(toPos(t),expr);
|
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 {
|
} else {
|
||||||
return eatPrimaryExpression();
|
return eatPrimaryExpression();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.expression.spel.standard;
|
package org.springframework.expression.spel.standard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,12 +30,12 @@ enum TokenKind {
|
||||||
DIV("/"), GE(">="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="),
|
DIV("/"), GE(">="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="),
|
||||||
MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"),
|
MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"),
|
||||||
SELECT("?["), POWER("^"),
|
SELECT("?["), POWER("^"),
|
||||||
ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&")
|
ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&"), INC("++"), DEC("--")
|
||||||
;
|
;
|
||||||
|
|
||||||
char[] tokenChars;
|
char[] tokenChars;
|
||||||
private boolean hasPayload; // is there more to this token than simply the kind
|
private boolean hasPayload; // is there more to this token than simply the kind
|
||||||
|
|
||||||
private TokenKind(String tokenString) {
|
private TokenKind(String tokenString) {
|
||||||
tokenChars = tokenString.toCharArray();
|
tokenChars = tokenString.toCharArray();
|
||||||
hasPayload = tokenChars.length==0;
|
hasPayload = tokenChars.length==0;
|
||||||
|
@ -43,15 +44,15 @@ enum TokenKind {
|
||||||
private TokenKind() {
|
private TokenKind() {
|
||||||
this("");
|
this("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.name()+(tokenChars.length!=0?"("+new String(tokenChars)+")":"");
|
return this.name()+(tokenChars.length!=0?"("+new String(tokenChars)+")":"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPayload() {
|
public boolean hasPayload() {
|
||||||
return hasPayload;
|
return hasPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLength() {
|
public int getLength() {
|
||||||
return tokenChars.length;
|
return tokenChars.length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,13 +55,21 @@ class Tokenizer {
|
||||||
} else {
|
} else {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '+':
|
case '+':
|
||||||
pushCharToken(TokenKind.PLUS);
|
if (isTwoCharToken(TokenKind.INC)) {
|
||||||
|
pushPairToken(TokenKind.INC);
|
||||||
|
} else {
|
||||||
|
pushCharToken(TokenKind.PLUS);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case '_': // the other way to start an identifier
|
case '_': // the other way to start an identifier
|
||||||
lexIdentifier();
|
lexIdentifier();
|
||||||
break;
|
break;
|
||||||
case '-':
|
case '-':
|
||||||
pushCharToken(TokenKind.MINUS);
|
if (isTwoCharToken(TokenKind.DEC)) {
|
||||||
|
pushPairToken(TokenKind.DEC);
|
||||||
|
} else {
|
||||||
|
pushCharToken(TokenKind.MINUS);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ':':
|
case ':':
|
||||||
pushCharToken(TokenKind.COLON);
|
pushCharToken(TokenKind.COLON);
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
import org.springframework.expression.AccessException;
|
import org.springframework.expression.AccessException;
|
||||||
|
import org.springframework.expression.BeanResolver;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
|
@ -43,7 +44,7 @@ import org.springframework.expression.spel.testresources.TestPerson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the evaluation of real expressions in a real context.
|
* Tests the evaluation of real expressions in a real context.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
@ -622,4 +623,765 @@ public class EvaluationTests extends ExpressionTestCase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// increment/decrement operators - SPR-9751
|
||||||
|
|
||||||
|
static class Spr9751 {
|
||||||
|
public String type = "hello";
|
||||||
|
public double ddd = 2.0d;
|
||||||
|
public float fff = 3.0f;
|
||||||
|
public long lll = 66666L;
|
||||||
|
public int iii = 42;
|
||||||
|
public short sss = (short)15;
|
||||||
|
public Spr9751_2 foo = new Spr9751_2();
|
||||||
|
|
||||||
|
public void m() {}
|
||||||
|
|
||||||
|
public int[] intArray = new int[]{1,2,3,4,5};
|
||||||
|
public int index1 = 2;
|
||||||
|
|
||||||
|
public Integer[] integerArray;
|
||||||
|
public int index2 = 2;
|
||||||
|
|
||||||
|
public List<String> listOfStrings;
|
||||||
|
public int index3 = 0;
|
||||||
|
|
||||||
|
public Spr9751() {
|
||||||
|
integerArray = new Integer[5];
|
||||||
|
integerArray[0] = 1;
|
||||||
|
integerArray[1] = 2;
|
||||||
|
integerArray[2] = 3;
|
||||||
|
integerArray[3] = 4;
|
||||||
|
integerArray[4] = 5;
|
||||||
|
listOfStrings = new ArrayList<String>();
|
||||||
|
listOfStrings.add("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEven(int i) {
|
||||||
|
return (i%2)==0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Spr9751_2 {
|
||||||
|
public int iii = 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test is checking that with the changes for 9751 that the refactoring in Indexer is
|
||||||
|
* coping correctly for references beyond collection boundaries.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void collectionGrowingViaIndexer() {
|
||||||
|
Spr9751 instance = new Spr9751();
|
||||||
|
|
||||||
|
// Add a new element to the list
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(instance);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = parser.parseExpression("listOfStrings[++index3]='def'");
|
||||||
|
e.getValue(ctx);
|
||||||
|
assertEquals(2,instance.listOfStrings.size());
|
||||||
|
assertEquals("def",instance.listOfStrings.get(1));
|
||||||
|
|
||||||
|
// Check reference beyond end of collection
|
||||||
|
ctx = new StandardEvaluationContext(instance);
|
||||||
|
parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
e = parser.parseExpression("listOfStrings[0]");
|
||||||
|
String value = e.getValue(ctx,String.class);
|
||||||
|
assertEquals("abc",value);
|
||||||
|
e = parser.parseExpression("listOfStrings[1]");
|
||||||
|
value = e.getValue(ctx,String.class);
|
||||||
|
assertEquals("def",value);
|
||||||
|
e = parser.parseExpression("listOfStrings[2]");
|
||||||
|
value = e.getValue(ctx,String.class);
|
||||||
|
assertEquals("",value);
|
||||||
|
|
||||||
|
// Now turn off growing and reference off the end
|
||||||
|
ctx = new StandardEvaluationContext(instance);
|
||||||
|
parser = new SpelExpressionParser(new SpelParserConfiguration(false, false));
|
||||||
|
e = parser.parseExpression("listOfStrings[3]");
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,String.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now I am making #this not assignable
|
||||||
|
@Test
|
||||||
|
public void increment01root() {
|
||||||
|
Integer i = 42;
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(i);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = parser.parseExpression("#this++");
|
||||||
|
assertEquals(42,i.intValue());
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void increment02postfix() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
// double
|
||||||
|
e = parser.parseExpression("ddd++");
|
||||||
|
assertEquals(2.0d,helper.ddd,0d);
|
||||||
|
double return_ddd = e.getValue(ctx,Double.TYPE);
|
||||||
|
assertEquals(2.0d,return_ddd,0d);
|
||||||
|
assertEquals(3.0d,helper.ddd,0d);
|
||||||
|
|
||||||
|
// float
|
||||||
|
e = parser.parseExpression("fff++");
|
||||||
|
assertEquals(3.0f,helper.fff,0d);
|
||||||
|
float return_fff = e.getValue(ctx,Float.TYPE);
|
||||||
|
assertEquals(3.0f,return_fff,0d);
|
||||||
|
assertEquals(4.0f,helper.fff,0d);
|
||||||
|
|
||||||
|
// long
|
||||||
|
e = parser.parseExpression("lll++");
|
||||||
|
assertEquals(66666L,helper.lll);
|
||||||
|
long return_lll = e.getValue(ctx,Long.TYPE);
|
||||||
|
assertEquals(66666L,return_lll);
|
||||||
|
assertEquals(66667L,helper.lll);
|
||||||
|
|
||||||
|
// int
|
||||||
|
e = parser.parseExpression("iii++");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
int return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,return_iii);
|
||||||
|
assertEquals(43,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(43,return_iii);
|
||||||
|
assertEquals(44,helper.iii);
|
||||||
|
|
||||||
|
// short
|
||||||
|
e = parser.parseExpression("sss++");
|
||||||
|
assertEquals(15,helper.sss);
|
||||||
|
short return_sss = e.getValue(ctx,Short.TYPE);
|
||||||
|
assertEquals(15,return_sss);
|
||||||
|
assertEquals(16,helper.sss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void increment02prefix() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
// double
|
||||||
|
e = parser.parseExpression("++ddd");
|
||||||
|
assertEquals(2.0d,helper.ddd,0d);
|
||||||
|
double return_ddd = e.getValue(ctx,Double.TYPE);
|
||||||
|
assertEquals(3.0d,return_ddd,0d);
|
||||||
|
assertEquals(3.0d,helper.ddd,0d);
|
||||||
|
|
||||||
|
// float
|
||||||
|
e = parser.parseExpression("++fff");
|
||||||
|
assertEquals(3.0f,helper.fff,0d);
|
||||||
|
float return_fff = e.getValue(ctx,Float.TYPE);
|
||||||
|
assertEquals(4.0f,return_fff,0d);
|
||||||
|
assertEquals(4.0f,helper.fff,0d);
|
||||||
|
|
||||||
|
// long
|
||||||
|
e = parser.parseExpression("++lll");
|
||||||
|
assertEquals(66666L,helper.lll);
|
||||||
|
long return_lll = e.getValue(ctx,Long.TYPE);
|
||||||
|
assertEquals(66667L,return_lll);
|
||||||
|
assertEquals(66667L,helper.lll);
|
||||||
|
|
||||||
|
// int
|
||||||
|
e = parser.parseExpression("++iii");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
int return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(43,return_iii);
|
||||||
|
assertEquals(43,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(44,return_iii);
|
||||||
|
assertEquals(44,helper.iii);
|
||||||
|
|
||||||
|
// short
|
||||||
|
e = parser.parseExpression("++sss");
|
||||||
|
assertEquals(15,helper.sss);
|
||||||
|
int return_sss = (Integer)e.getValue(ctx);
|
||||||
|
assertEquals(16,return_sss);
|
||||||
|
assertEquals(16,helper.sss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void increment03() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
e = parser.parseExpression("m()++");
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,Double.TYPE);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.OPERAND_NOT_INCREMENTABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
e = parser.parseExpression("++m()");
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,Double.TYPE);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.OPERAND_NOT_INCREMENTABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void increment04() {
|
||||||
|
Integer i = 42;
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(i);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
try {
|
||||||
|
Expression e = parser.parseExpression("++1");
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Expression e = parser.parseExpression("1++");
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void decrement01root() {
|
||||||
|
Integer i = 42;
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(i);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = parser.parseExpression("#this--");
|
||||||
|
assertEquals(42,i.intValue());
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decrement02postfix() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
// double
|
||||||
|
e = parser.parseExpression("ddd--");
|
||||||
|
assertEquals(2.0d,helper.ddd,0d);
|
||||||
|
double return_ddd = e.getValue(ctx,Double.TYPE);
|
||||||
|
assertEquals(2.0d,return_ddd,0d);
|
||||||
|
assertEquals(1.0d,helper.ddd,0d);
|
||||||
|
|
||||||
|
// float
|
||||||
|
e = parser.parseExpression("fff--");
|
||||||
|
assertEquals(3.0f,helper.fff,0d);
|
||||||
|
float return_fff = e.getValue(ctx,Float.TYPE);
|
||||||
|
assertEquals(3.0f,return_fff,0d);
|
||||||
|
assertEquals(2.0f,helper.fff,0d);
|
||||||
|
|
||||||
|
// long
|
||||||
|
e = parser.parseExpression("lll--");
|
||||||
|
assertEquals(66666L,helper.lll);
|
||||||
|
long return_lll = e.getValue(ctx,Long.TYPE);
|
||||||
|
assertEquals(66666L,return_lll);
|
||||||
|
assertEquals(66665L,helper.lll);
|
||||||
|
|
||||||
|
// int
|
||||||
|
e = parser.parseExpression("iii--");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
int return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,return_iii);
|
||||||
|
assertEquals(41,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(41,return_iii);
|
||||||
|
assertEquals(40,helper.iii);
|
||||||
|
|
||||||
|
// short
|
||||||
|
e = parser.parseExpression("sss--");
|
||||||
|
assertEquals(15,helper.sss);
|
||||||
|
short return_sss = e.getValue(ctx,Short.TYPE);
|
||||||
|
assertEquals(15,return_sss);
|
||||||
|
assertEquals(14,helper.sss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decrement02prefix() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
// double
|
||||||
|
e = parser.parseExpression("--ddd");
|
||||||
|
assertEquals(2.0d,helper.ddd,0d);
|
||||||
|
double return_ddd = e.getValue(ctx,Double.TYPE);
|
||||||
|
assertEquals(1.0d,return_ddd,0d);
|
||||||
|
assertEquals(1.0d,helper.ddd,0d);
|
||||||
|
|
||||||
|
// float
|
||||||
|
e = parser.parseExpression("--fff");
|
||||||
|
assertEquals(3.0f,helper.fff,0d);
|
||||||
|
float return_fff = e.getValue(ctx,Float.TYPE);
|
||||||
|
assertEquals(2.0f,return_fff,0d);
|
||||||
|
assertEquals(2.0f,helper.fff,0d);
|
||||||
|
|
||||||
|
// long
|
||||||
|
e = parser.parseExpression("--lll");
|
||||||
|
assertEquals(66666L,helper.lll);
|
||||||
|
long return_lll = e.getValue(ctx,Long.TYPE);
|
||||||
|
assertEquals(66665L,return_lll);
|
||||||
|
assertEquals(66665L,helper.lll);
|
||||||
|
|
||||||
|
// int
|
||||||
|
e = parser.parseExpression("--iii");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
int return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(41,return_iii);
|
||||||
|
assertEquals(41,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(40,return_iii);
|
||||||
|
assertEquals(40,helper.iii);
|
||||||
|
|
||||||
|
// short
|
||||||
|
e = parser.parseExpression("--sss");
|
||||||
|
assertEquals(15,helper.sss);
|
||||||
|
int return_sss = (Integer)e.getValue(ctx);
|
||||||
|
assertEquals(14,return_sss);
|
||||||
|
assertEquals(14,helper.sss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decrement03() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
e = parser.parseExpression("m()--");
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,Double.TYPE);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.OPERAND_NOT_DECREMENTABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
e = parser.parseExpression("--m()");
|
||||||
|
try {
|
||||||
|
e.getValue(ctx,Double.TYPE);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.OPERAND_NOT_DECREMENTABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decrement04() {
|
||||||
|
Integer i = 42;
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(i);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
try {
|
||||||
|
Expression e = parser.parseExpression("--1");
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Expression e = parser.parseExpression("1--");
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
assertEquals(SpelMessage.NOT_ASSIGNABLE,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void incdecTogether() {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
// index1 is 2 at the start - the 'intArray[#root.index1++]' should not be evaluated twice!
|
||||||
|
// intArray[2] is 3
|
||||||
|
e = parser.parseExpression("intArray[#root.index1++]++");
|
||||||
|
e.getValue(ctx,Integer.class);
|
||||||
|
assertEquals(3,helper.index1);
|
||||||
|
assertEquals(4,helper.intArray[2]);
|
||||||
|
|
||||||
|
// index1 is 3 intArray[3] is 4
|
||||||
|
e = parser.parseExpression("intArray[#root.index1++]--");
|
||||||
|
assertEquals(4,e.getValue(ctx,Integer.class).intValue());
|
||||||
|
assertEquals(4,helper.index1);
|
||||||
|
assertEquals(3,helper.intArray[3]);
|
||||||
|
|
||||||
|
// index1 is 4, intArray[3] is 3
|
||||||
|
e = parser.parseExpression("intArray[--#root.index1]++");
|
||||||
|
assertEquals(3,e.getValue(ctx,Integer.class).intValue());
|
||||||
|
assertEquals(3,helper.index1);
|
||||||
|
assertEquals(4,helper.intArray[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) {
|
||||||
|
try {
|
||||||
|
Expression e = parser.parseExpression(expressionString);
|
||||||
|
SpelUtilities.printAbstractSyntaxTree(System.out, e);
|
||||||
|
e.getValue(eContext);
|
||||||
|
fail();
|
||||||
|
} catch (SpelEvaluationException see) {
|
||||||
|
see.printStackTrace();
|
||||||
|
assertEquals(messageCode,see.getMessageCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectFailNotAssignable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
|
||||||
|
expectFail(parser,eContext,expressionString,SpelMessage.NOT_ASSIGNABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectFailSetValueNotSupported(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
|
||||||
|
expectFail(parser,eContext,expressionString,SpelMessage.SETVALUE_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectFailNotIncrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
|
||||||
|
expectFail(parser,eContext,expressionString,SpelMessage.OPERAND_NOT_INCREMENTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectFailNotDecrementable(ExpressionParser parser, EvaluationContext eContext, String expressionString) {
|
||||||
|
expectFail(parser,eContext,expressionString,SpelMessage.OPERAND_NOT_DECREMENTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify how all the nodes behave with assignment (++, --, =)
|
||||||
|
@Test
|
||||||
|
public void incrementAllNodeTypes() throws SecurityException, NoSuchMethodException {
|
||||||
|
Spr9751 helper = new Spr9751();
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext(helper);
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||||
|
Expression e = null;
|
||||||
|
|
||||||
|
// BooleanLiteral
|
||||||
|
expectFailNotAssignable(parser, ctx, "true++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--false");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "true=false");
|
||||||
|
|
||||||
|
// IntLiteral
|
||||||
|
expectFailNotAssignable(parser, ctx, "12++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--1222");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "12=16");
|
||||||
|
|
||||||
|
// LongLiteral
|
||||||
|
expectFailNotAssignable(parser, ctx, "1.0d++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--3.4d");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "1.0d=3.2d");
|
||||||
|
|
||||||
|
// NullLiteral
|
||||||
|
expectFailNotAssignable(parser, ctx, "null++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--null");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "null=null");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "null=123");
|
||||||
|
|
||||||
|
// OpAnd
|
||||||
|
expectFailNotAssignable(parser, ctx, "(true && false)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(false AND true)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(true && false)=(false && true)");
|
||||||
|
|
||||||
|
// OpDivide
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3/4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2/5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1/2)=(3/4)");
|
||||||
|
|
||||||
|
// OpEq
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3==4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2==5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1==2)=(3==4)");
|
||||||
|
|
||||||
|
// OpGE
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3>=4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2>=5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1>=2)=(3>=4)");
|
||||||
|
|
||||||
|
// OpGT
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3>4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2>5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1>2)=(3>4)");
|
||||||
|
|
||||||
|
// OpLE
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3<=4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2<=5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1<=2)=(3<=4)");
|
||||||
|
|
||||||
|
// OpLT
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3<4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2<5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1<2)=(3<4)");
|
||||||
|
|
||||||
|
// OpMinus
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3-4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2-5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1-2)=(3-4)");
|
||||||
|
|
||||||
|
// OpModulus
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3%4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2%5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1%2)=(3%4)");
|
||||||
|
|
||||||
|
// OpMultiply
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3*4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2*5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1*2)=(3*4)");
|
||||||
|
|
||||||
|
// OpNE
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3!=4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2!=5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1!=2)=(3!=4)");
|
||||||
|
|
||||||
|
// OpOr
|
||||||
|
expectFailNotAssignable(parser, ctx, "(true || false)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(false OR true)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(true || false)=(false OR true)");
|
||||||
|
|
||||||
|
// OpPlus
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3+4)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(2+5)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1+2)=(3+4)");
|
||||||
|
|
||||||
|
// RealLiteral
|
||||||
|
expectFailNotAssignable(parser, ctx, "1.0d++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--2.0d");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1.0d)=(3.0d)");
|
||||||
|
expectFailNotAssignable(parser, ctx, "1.0f++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--2.0f");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(1.0f)=(3.0f)");
|
||||||
|
|
||||||
|
// StringLiteral
|
||||||
|
expectFailNotAssignable(parser, ctx, "'abc'++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--'def'");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "'abc'='def'");
|
||||||
|
|
||||||
|
// Ternary
|
||||||
|
expectFailNotAssignable(parser, ctx, "(true?true:false)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(true?true:false)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(true?true:false)=(true?true:false)");
|
||||||
|
|
||||||
|
// TypeReference
|
||||||
|
expectFailNotAssignable(parser, ctx, "T(String)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--T(Integer)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "T(String)=T(Integer)");
|
||||||
|
|
||||||
|
// OperatorBetween
|
||||||
|
expectFailNotAssignable(parser, ctx, "(3 between {1,5})++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(3 between {1,5})");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(3 between {1,5})=(3 between {1,5})");
|
||||||
|
|
||||||
|
// OperatorInstanceOf
|
||||||
|
expectFailNotAssignable(parser, ctx, "(type instanceof T(String))++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(type instanceof T(String))");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(type instanceof T(String))=(type instanceof T(String))");
|
||||||
|
|
||||||
|
// Elvis
|
||||||
|
expectFailNotAssignable(parser, ctx, "(true?:false)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(true?:false)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(true?:false)=(true?:false)");
|
||||||
|
|
||||||
|
// OpInc
|
||||||
|
expectFailNotAssignable(parser, ctx, "(iii++)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(++iii)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(iii++)=(++iii)");
|
||||||
|
|
||||||
|
// OpDec
|
||||||
|
expectFailNotAssignable(parser, ctx, "(iii--)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(--iii)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(iii--)=(--iii)");
|
||||||
|
|
||||||
|
// OperatorNot
|
||||||
|
expectFailNotAssignable(parser, ctx, "(!true)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(!false)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(!true)=(!false)");
|
||||||
|
|
||||||
|
// OperatorPower
|
||||||
|
expectFailNotAssignable(parser, ctx, "(iii^2)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(iii^2)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(iii^2)=(iii^3)");
|
||||||
|
|
||||||
|
// Assign
|
||||||
|
// iii=42
|
||||||
|
e = parser.parseExpression("iii=iii++");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
int return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
assertEquals(42,return_iii);
|
||||||
|
|
||||||
|
// Identifier
|
||||||
|
e = parser.parseExpression("iii++");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,return_iii);
|
||||||
|
assertEquals(43,helper.iii);
|
||||||
|
|
||||||
|
e = parser.parseExpression("--iii");
|
||||||
|
assertEquals(43,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,return_iii);
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
|
||||||
|
e = parser.parseExpression("iii=99");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
return_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(99,return_iii);
|
||||||
|
assertEquals(99,helper.iii);
|
||||||
|
|
||||||
|
// CompoundExpression
|
||||||
|
// foo.iii == 99
|
||||||
|
e = parser.parseExpression("foo.iii++");
|
||||||
|
assertEquals(99,helper.foo.iii);
|
||||||
|
int return_foo_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(99,return_foo_iii);
|
||||||
|
assertEquals(100,helper.foo.iii);
|
||||||
|
|
||||||
|
e = parser.parseExpression("--foo.iii");
|
||||||
|
assertEquals(100,helper.foo.iii);
|
||||||
|
return_foo_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(99,return_foo_iii);
|
||||||
|
assertEquals(99,helper.foo.iii);
|
||||||
|
|
||||||
|
e = parser.parseExpression("foo.iii=999");
|
||||||
|
assertEquals(99,helper.foo.iii);
|
||||||
|
return_foo_iii = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(999,return_foo_iii);
|
||||||
|
assertEquals(999,helper.foo.iii);
|
||||||
|
|
||||||
|
// ConstructorReference
|
||||||
|
expectFailNotAssignable(parser, ctx, "(new String('abc'))++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--(new String('abc'))");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "(new String('abc'))=(new String('abc'))");
|
||||||
|
|
||||||
|
// MethodReference
|
||||||
|
expectFailNotIncrementable(parser, ctx, "m()++");
|
||||||
|
expectFailNotDecrementable(parser, ctx, "--m()");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "m()=m()");
|
||||||
|
|
||||||
|
// OperatorMatches
|
||||||
|
expectFailNotAssignable(parser, ctx, "('abc' matches '^a..')++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--('abc' matches '^a..')");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "('abc' matches '^a..')=('abc' matches '^a..')");
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
ctx.registerFunction("isEven", Spr9751.class.getDeclaredMethod("isEven", Integer.TYPE));
|
||||||
|
|
||||||
|
expectFailNotIncrementable(parser, ctx, "({1,2,3}.?[#isEven(#this)])++");
|
||||||
|
expectFailNotDecrementable(parser, ctx, "--({1,2,3}.?[#isEven(#this)])");
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3}.?[#isEven(#this)])=({1,2,3}.?[#isEven(#this)])");
|
||||||
|
|
||||||
|
// slightly diff here because return value isn't a list, it is a single entity
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3}.^[#isEven(#this)])++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--({1,2,3}.^[#isEven(#this)])");
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3}.^[#isEven(#this)])=({1,2,3}.^[#isEven(#this)])");
|
||||||
|
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3}.$[#isEven(#this)])++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--({1,2,3}.$[#isEven(#this)])");
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3}.$[#isEven(#this)])=({1,2,3}.$[#isEven(#this)])");
|
||||||
|
|
||||||
|
// FunctionReference
|
||||||
|
expectFailNotAssignable(parser, ctx, "#isEven(3)++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--#isEven(4)");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "#isEven(3)=#isEven(5)");
|
||||||
|
|
||||||
|
// VariableReference
|
||||||
|
ctx.setVariable("wibble", "hello world");
|
||||||
|
expectFailNotIncrementable(parser, ctx, "#wibble++");
|
||||||
|
expectFailNotDecrementable(parser, ctx, "--#wibble");
|
||||||
|
e = parser.parseExpression("#wibble=#wibble+#wibble");
|
||||||
|
String s = e.getValue(ctx,String.class);
|
||||||
|
assertEquals("hello worldhello world",s);
|
||||||
|
assertEquals("hello worldhello world",(String)ctx.lookupVariable("wibble"));
|
||||||
|
|
||||||
|
ctx.setVariable("wobble", 3);
|
||||||
|
e = parser.parseExpression("#wobble++");
|
||||||
|
assertEquals(3,((Integer)ctx.lookupVariable("wobble")).intValue());
|
||||||
|
int r = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(3,r);
|
||||||
|
assertEquals(4,((Integer)ctx.lookupVariable("wobble")).intValue());
|
||||||
|
|
||||||
|
e = parser.parseExpression("--#wobble");
|
||||||
|
assertEquals(4,((Integer)ctx.lookupVariable("wobble")).intValue());
|
||||||
|
r = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(3,r);
|
||||||
|
assertEquals(3,((Integer)ctx.lookupVariable("wobble")).intValue());
|
||||||
|
|
||||||
|
e = parser.parseExpression("#wobble=34");
|
||||||
|
assertEquals(3,((Integer)ctx.lookupVariable("wobble")).intValue());
|
||||||
|
r = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(34,r);
|
||||||
|
assertEquals(34,((Integer)ctx.lookupVariable("wobble")).intValue());
|
||||||
|
|
||||||
|
// Projection
|
||||||
|
expectFailNotIncrementable(parser, ctx, "({1,2,3}.![#isEven(#this)])++"); // projection would be {false,true,false}
|
||||||
|
expectFailNotDecrementable(parser, ctx, "--({1,2,3}.![#isEven(#this)])"); // projection would be {false,true,false}
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3}.![#isEven(#this)])=({1,2,3}.![#isEven(#this)])");
|
||||||
|
|
||||||
|
// InlineList
|
||||||
|
expectFailNotAssignable(parser, ctx, "({1,2,3})++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--({1,2,3})");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "({1,2,3})=({1,2,3})");
|
||||||
|
|
||||||
|
// BeanReference
|
||||||
|
ctx.setBeanResolver(new MyBeanResolver());
|
||||||
|
expectFailNotAssignable(parser, ctx, "@foo++");
|
||||||
|
expectFailNotAssignable(parser, ctx, "--@foo");
|
||||||
|
expectFailSetValueNotSupported(parser, ctx, "@foo=@bar");
|
||||||
|
|
||||||
|
// PropertyOrFieldReference
|
||||||
|
helper.iii = 42;
|
||||||
|
e = parser.parseExpression("iii++");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
r = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,r);
|
||||||
|
assertEquals(43,helper.iii);
|
||||||
|
|
||||||
|
e = parser.parseExpression("--iii");
|
||||||
|
assertEquals(43,helper.iii);
|
||||||
|
r = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(42,r);
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
|
||||||
|
e = parser.parseExpression("iii=100");
|
||||||
|
assertEquals(42,helper.iii);
|
||||||
|
r = e.getValue(ctx,Integer.TYPE);
|
||||||
|
assertEquals(100,r);
|
||||||
|
assertEquals(100,helper.iii);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyBeanResolver implements BeanResolver {
|
||||||
|
|
||||||
|
public Object resolve(EvaluationContext context, String beanName)
|
||||||
|
throws AccessException {
|
||||||
|
if (beanName.equals("foo") || beanName.equals("bar")) {
|
||||||
|
return new Spr9751_2();
|
||||||
|
}
|
||||||
|
throw new AccessException("not heard of "+beanName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.expression.spel;
|
package org.springframework.expression.spel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -22,14 +23,13 @@ import junit.framework.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.expression.spel.standard.SpelExpression;
|
import org.springframework.expression.spel.standard.SpelExpression;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are tests for language features that are not yet considered 'live'. Either missing implementation or
|
* These are tests for language features that are not yet considered 'live'. Either missing implementation or
|
||||||
* documentation.
|
* documentation.
|
||||||
*
|
*
|
||||||
* Where implementation is missing the tests are commented out.
|
* Where implementation is missing the tests are commented out.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
*/
|
*/
|
||||||
public class InProgressTests extends ExpressionTestCase {
|
public class InProgressTests extends ExpressionTestCase {
|
||||||
|
@ -37,7 +37,8 @@ public class InProgressTests extends ExpressionTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testRelOperatorsBetween01() {
|
public void testRelOperatorsBetween01() {
|
||||||
evaluate("1 between listOneFive", "true", Boolean.class);
|
evaluate("1 between listOneFive", "true", Boolean.class);
|
||||||
// evaluate("1 between {1, 5}", "true", Boolean.class); // no inline list building at the moment
|
// no inline list building at the moment
|
||||||
|
// evaluate("1 between {1, 5}", "true", Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -78,7 +79,6 @@ public class InProgressTests extends ExpressionTestCase {
|
||||||
public void testProjection06() throws Exception {
|
public void testProjection06() throws Exception {
|
||||||
SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.![true]");
|
SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.![true]");
|
||||||
Assert.assertEquals("'abc'.![true]", expr.toStringAST());
|
Assert.assertEquals("'abc'.![true]", expr.toStringAST());
|
||||||
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SELECTION
|
// SELECTION
|
||||||
|
@ -99,7 +99,6 @@ public class InProgressTests extends ExpressionTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testSelection03() {
|
public void testSelection03() {
|
||||||
evaluate("mapOfNumbersUpToTen.?[key>5].size()", "5", Integer.class);
|
evaluate("mapOfNumbersUpToTen.?[key>5].size()", "5", Integer.class);
|
||||||
// evaluate("listOfNumbersUpToTen.?{#this>5}", "5", ArrayList.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -143,284 +142,16 @@ public class InProgressTests extends ExpressionTestCase {
|
||||||
public void testSelectionAST() throws Exception {
|
public void testSelectionAST() throws Exception {
|
||||||
SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.^[true]");
|
SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.^[true]");
|
||||||
Assert.assertEquals("'abc'.^[true]", expr.toStringAST());
|
Assert.assertEquals("'abc'.^[true]", expr.toStringAST());
|
||||||
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
|
||||||
expr = (SpelExpression) parser.parseExpression("'abc'.?[true]");
|
expr = (SpelExpression) parser.parseExpression("'abc'.?[true]");
|
||||||
Assert.assertEquals("'abc'.?[true]", expr.toStringAST());
|
Assert.assertEquals("'abc'.?[true]", expr.toStringAST());
|
||||||
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
|
||||||
expr = (SpelExpression) parser.parseExpression("'abc'.$[true]");
|
expr = (SpelExpression) parser.parseExpression("'abc'.$[true]");
|
||||||
Assert.assertEquals("'abc'.$[true]", expr.toStringAST());
|
Assert.assertEquals("'abc'.$[true]", expr.toStringAST());
|
||||||
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor invocation
|
// Constructor invocation
|
||||||
|
|
||||||
// public void testPrimitiveTypeArrayConstructors() {
|
|
||||||
// evaluate("new int[]{1,2,3,4}.count()", 4, Integer.class);
|
|
||||||
// evaluate("new boolean[]{true,false,true}.count()", 3, Integer.class);
|
|
||||||
// evaluate("new char[]{'a','b','c'}.count()", 3, Integer.class);
|
|
||||||
// evaluate("new long[]{1,2,3,4,5}.count()", 5, Integer.class);
|
|
||||||
// evaluate("new short[]{2,3,4,5,6}.count()", 5, Integer.class);
|
|
||||||
// evaluate("new double[]{1d,2d,3d,4d}.count()", 4, Integer.class);
|
|
||||||
// evaluate("new float[]{1f,2f,3f,4f}.count()", 4, Integer.class);
|
|
||||||
// evaluate("new byte[]{1,2,3,4}.count()", 4, Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testPrimitiveTypeArrayConstructorsElements() {
|
|
||||||
// evaluate("new int[]{1,2,3,4}[0]", 1, Integer.class);
|
|
||||||
// evaluate("new boolean[]{true,false,true}[0]", true, Boolean.class);
|
|
||||||
// evaluate("new char[]{'a','b','c'}[0]", 'a', Character.class);
|
|
||||||
// evaluate("new long[]{1,2,3,4,5}[0]", 1L, Long.class);
|
|
||||||
// evaluate("new short[]{2,3,4,5,6}[0]", (short) 2, Short.class);
|
|
||||||
// evaluate("new double[]{1d,2d,3d,4d}[0]", (double) 1, Double.class);
|
|
||||||
// evaluate("new float[]{1f,2f,3f,4f}[0]", (float) 1, Float.class);
|
|
||||||
// evaluate("new byte[]{1,2,3,4}[0]", (byte) 1, Byte.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testErrorCases() {
|
|
||||||
// evaluateAndCheckError("new char[7]{'a','c','d','e'}", SpelMessages.INITIALIZER_LENGTH_INCORRECT);
|
|
||||||
// evaluateAndCheckError("new char[3]{'a','c','d','e'}", SpelMessages.INITIALIZER_LENGTH_INCORRECT);
|
|
||||||
// evaluateAndCheckError("new char[2]{'hello','world'}", SpelMessages.TYPE_CONVERSION_ERROR);
|
|
||||||
// evaluateAndCheckError("new String('a','c','d')", SpelMessages.CONSTRUCTOR_NOT_FOUND);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testTypeArrayConstructors() {
|
|
||||||
// evaluate("new String[]{'a','b','c','d'}[1]", "b", String.class);
|
|
||||||
// evaluateAndCheckError("new String[]{'a','b','c','d'}.size()", SpelMessages.METHOD_NOT_FOUND, 30, "size()",
|
|
||||||
// "java.lang.String[]");
|
|
||||||
// evaluateAndCheckError("new String[]{'a','b','c','d'}.juggernaut", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 30,
|
|
||||||
// "juggernaut", "java.lang.String[]");
|
|
||||||
// evaluate("new String[]{'a','b','c','d'}.length", 4, Integer.class);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void testMultiDimensionalArrays() {
|
|
||||||
// evaluate(
|
|
||||||
// "new String[3,4]",
|
|
||||||
// "[Ljava.lang.String;[3]{java.lang.String[4]{null,null,null,null},java.lang.String[4]{null,null,null,null},java.lang.String[4]{null,null,null,null}}"
|
|
||||||
// ,
|
|
||||||
// new String[3][4].getClass());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// evaluate("new String(new char[]{'h','e','l','l','o'})", "hello", String.class);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// public void testRelOperatorsIn01() {
|
|
||||||
// evaluate("3 in {1,2,3,4,5}", "true", Boolean.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testRelOperatorsIn02() {
|
|
||||||
// evaluate("name in {null, \"Nikola Tesla\"}", "true", Boolean.class);
|
|
||||||
// evaluate("name in {null, \"Anonymous\"}", "false", Boolean.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// public void testRelOperatorsBetween02() {
|
|
||||||
// evaluate("'efg' between {'abc', 'xyz'}", "true", Boolean.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// public void testRelOperatorsBetweenErrors02() {
|
|
||||||
// evaluateAndCheckError("'abc' between {5,7}", SpelMessages.NOT_COMPARABLE, 6);
|
|
||||||
// }
|
|
||||||
// Lambda calculations
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// public void testLambda02() {
|
|
||||||
// evaluate("(#max={|x,y| $x > $y ? $x : $y };true)", "true", Boolean.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testLambdaMax() {
|
|
||||||
// evaluate("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "25", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testLambdaFactorial01() {
|
|
||||||
// evaluate("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", "120", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testLambdaFactorial02() {
|
|
||||||
// evaluate("(#fact = {|n| $n <= 1 ? 1 : #fact($n-1) * $n }; #fact(5))", "120", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testLambdaAlphabet01() {
|
|
||||||
// evaluate("(#alpha = {|l,s| $l>'z'?$s:#alpha($l+1,$s+$l)};#alphabet={||#alpha('a','')}; #alphabet())",
|
|
||||||
// "abcdefghijklmnopqrstuvwxyz", String.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testLambdaAlphabet02() {
|
|
||||||
// evaluate("(#alphabet = {|l,s| $l>'z'?$s:#alphabet($l+1,$s+$l)};#alphabet('a',''))",
|
|
||||||
// "abcdefghijklmnopqrstuvwxyz", String.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testLambdaDelegation01() {
|
|
||||||
// evaluate("(#sqrt={|n| T(Math).sqrt($n)};#delegate={|f,n| $f($n)};#delegate(#sqrt,4))", "2.0", Double.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testVariableReferences() {
|
|
||||||
// evaluate("(#answer=42;#answer)", "42", Integer.class, true);
|
|
||||||
// evaluate("($answer=42;$answer)", "42", Integer.class, true);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // inline map creation
|
|
||||||
// @Test
|
|
||||||
// public void testInlineMapCreation01() {
|
|
||||||
// evaluate("#{'key1':'Value 1', 'today':'Monday'}", "{key1=Value 1, today=Monday}", HashMap.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testInlineMapCreation02() {
|
|
||||||
// // "{2=February, 1=January, 3=March}", HashMap.class);
|
|
||||||
// evaluate("#{1:'January', 2:'February', 3:'March'}.size()", 3, Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testInlineMapCreation03() {
|
|
||||||
// evaluate("#{'key1':'Value 1', 'today':'Monday'}['key1']", "Value 1", String.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testInlineMapCreation04() {
|
|
||||||
// evaluate("#{1:'January', 2:'February', 3:'March'}[3]", "March", String.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testInlineMapCreation05() {
|
|
||||||
// evaluate("#{1:'January', 2:'February', 3:'March'}.get(2)", "February", String.class);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// set construction
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetConstruction01() {
|
public void testSetConstruction01() {
|
||||||
evaluate("new java.util.HashSet().addAll({'a','b','c'})", "true", Boolean.class);
|
evaluate("new java.util.HashSet().addAll({'a','b','c'})", "true", Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// public void testConstructorInvocation02() {
|
|
||||||
// evaluate("new String[3]", "java.lang.String[3]{null,null,null}", String[].class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testConstructorInvocation03() {
|
|
||||||
// evaluateAndCheckError("new String[]", SpelMessages.NO_SIZE_OR_INITIALIZER_FOR_ARRAY_CONSTRUCTION, 4);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testConstructorInvocation04() {
|
|
||||||
// evaluateAndCheckError("new String[3]{'abc',3,'def'}", SpelMessages.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, 4);
|
|
||||||
// }
|
|
||||||
// array construction
|
|
||||||
// @Test
|
|
||||||
// public void testArrayConstruction01() {
|
|
||||||
// evaluate("new int[] {1, 2, 3, 4, 5}", "int[5]{1,2,3,4,5}", int[].class);
|
|
||||||
// }
|
|
||||||
// public void testArrayConstruction02() {
|
|
||||||
// evaluate("new String[] {'abc', 'xyz'}", "java.lang.String[2]{abc,xyz}", String[].class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// collection processors
|
|
||||||
// from spring.net: count,sum,max,min,average,sort,orderBy,distinct,nonNull
|
|
||||||
// public void testProcessorsCount01() {
|
|
||||||
// evaluate("new String[] {'abc','def','xyz'}.count()", "3", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsCount02() {
|
|
||||||
// evaluate("new int[] {1,2,3}.count()", "3", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsMax01() {
|
|
||||||
// evaluate("new int[] {1,2,3}.max()", "3", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsMin01() {
|
|
||||||
// evaluate("new int[] {1,2,3}.min()", "1", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsKeys01() {
|
|
||||||
// evaluate("#{1:'January', 2:'February', 3:'March'}.keySet().sort()", "[1, 2, 3]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsValues01() {
|
|
||||||
// evaluate("#{1:'January', 2:'February', 3:'March'}.values().sort()", "[February, January, March]",
|
|
||||||
// ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsAverage01() {
|
|
||||||
// evaluate("new int[] {1,2,3}.average()", "2", Integer.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProcessorsSort01() {
|
|
||||||
// evaluate("new int[] {3,2,1}.sort()", "int[3]{1,2,3}", int[].class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testCollectionProcessorsNonNull01() {
|
|
||||||
// evaluate("{'a','b',null,'d',null}.nonnull()", "[a, b, d]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testCollectionProcessorsDistinct01() {
|
|
||||||
// evaluate("{'a','b','a','d','e'}.distinct()", "[a, b, d, e]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProjection03() {
|
|
||||||
// evaluate("{1,2,3,4,5,6,7,8,9,10}.!{#this>5}",
|
|
||||||
// "[false, false, false, false, false, true, true, true, true, true]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testProjection04() {
|
|
||||||
// evaluate("{1,2,3,4,5,6,7,8,9,10}.!{$index>5?'y':'n'}", "[n, n, n, n, n, n, y, y, y, y]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
// Bean references
|
|
||||||
// public void testReferences01() {
|
|
||||||
// evaluate("@(apple).name", "Apple", String.class, true);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testReferences02() {
|
|
||||||
// evaluate("@(fruits:banana).name", "Banana", String.class, true);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testReferences03() {
|
|
||||||
// evaluate("@(a.b.c)", null, null);
|
|
||||||
// } // null - no context, a.b.c treated as name
|
|
||||||
//
|
|
||||||
// public void testReferences05() {
|
|
||||||
// evaluate("@(a/b/c:orange).name", "Orange", String.class, true);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testReferences06() {
|
|
||||||
// evaluate("@(apple).color.getRGB() == T(java.awt.Color).green.getRGB()", "true", Boolean.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testReferences07() {
|
|
||||||
// evaluate("@(apple).color.getRGB().equals(T(java.awt.Color).green.getRGB())", "true", Boolean.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// value is not public, it is accessed through getRGB()
|
|
||||||
// public void testStaticRef01() {
|
|
||||||
// evaluate("T(Color).green.value!=0", "true", Boolean.class);
|
|
||||||
// }
|
|
||||||
// Indexer
|
|
||||||
// public void testCutProcessor01() {
|
|
||||||
// evaluate("{1,2,3,4,5}.cut(1,3)", "[2, 3, 4]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testCutProcessor02() {
|
|
||||||
// evaluate("{1,2,3,4,5}.cut(3,1)", "[4, 3, 2]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
// Ternary operator
|
|
||||||
// public void testTernaryOperator01() {
|
|
||||||
// evaluate("{1}.#isEven(#this[0]) == 'y'?'it is even':'it is odd'", "it is odd", String.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testTernaryOperator02() {
|
|
||||||
// evaluate("{2}.#isEven(#this[0]) == 'y'?'it is even':'it is odd'", "it is even", String.class);
|
|
||||||
// }
|
|
||||||
// public void testSelectionUsingIndex() {
|
|
||||||
// evaluate("{1,2,3,4,5,6,7,8,9,10}.?{$index > 5 }", "[7, 8, 9, 10]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
// public void testSelection01() {
|
|
||||||
// inline list creation not supported:
|
|
||||||
// evaluate("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}", "[2, 4, 6, 8, 10]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void testSelectionUsingIndex() {
|
|
||||||
// evaluate("listOfNumbersUpToTen.?[#index > 5 ]", "[7, 8, 9, 10]", ArrayList.class);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue