From f64325882da41bb128dec1b6d687b6eb6525623f Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Tue, 23 Oct 2012 12:00:22 -0700 Subject: [PATCH] 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 --- .../expression/spel/ExpressionState.java | 7 +- .../expression/spel/SpelMessage.java | 5 +- .../expression/spel/ast/AstUtils.java | 6 +- .../spel/ast/CompoundExpression.java | 77 +- .../expression/spel/ast/Indexer.java | 613 +++++++++----- .../expression/spel/ast/MethodReference.java | 117 ++- .../expression/spel/ast/OpDec.java | 114 +++ .../expression/spel/ast/OpInc.java | 112 +++ .../expression/spel/ast/Projection.java | 17 +- .../spel/ast/PropertyOrFieldReference.java | 109 ++- .../expression/spel/ast/Selection.java | 34 +- .../expression/spel/ast/SpelNodeImpl.java | 8 +- .../expression/spel/ast/ValueRef.java | 107 +++ .../spel/ast/VariableReference.java | 44 +- .../InternalSpelExpressionParser.java | 72 +- .../expression/spel/standard/TokenKind.java | 13 +- .../expression/spel/standard/Tokenizer.java | 12 +- .../expression/spel/EvaluationTests.java | 764 +++++++++++++++++- .../expression/spel/InProgressTests.java | 281 +------ 19 files changed, 1838 insertions(+), 674 deletions(-) create mode 100644 spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java create mode 100644 spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java create mode 100644 spring-expression/src/main/java/org/springframework/expression/spel/ast/ValueRef.java diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index 09f51cfe81..47ee693d82 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.springframework.expression.Operation; import org.springframework.expression.OperatorOverloader; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypeComparator; +import org.springframework.expression.TypeConverter; import org.springframework.expression.TypedValue; /** @@ -140,6 +141,10 @@ public class ExpressionState { return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor); } + public TypeConverter getTypeConverter() { + return this.relatedContext.getTypeConverter(); + } + public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { Object val = value.getValue(); return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index 23d616e1bc..5ee88152eb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -104,7 +104,10 @@ public enum SpelMessage { MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), // INITIALIZER_LENGTH_INCORRECT( Kind.ERROR, 1064, "array initializer size does not match array dimensions"), // - UNEXPECTED_ESCAPE_CHAR(Kind.ERROR,1065,"unexpected escape character."); + UNEXPECTED_ESCAPE_CHAR(Kind.ERROR,1065,"unexpected escape character."), // + OPERAND_NOT_INCREMENTABLE(Kind.ERROR,1066,"the expression component ''{0}'' does not support increment"), // + OPERAND_NOT_DECREMENTABLE(Kind.ERROR,1067,"the expression component ''{0}'' does not support decrement"), // + NOT_ASSIGNABLE(Kind.ERROR,1068,"the expression component ''{0}'' is not assignable"), // ; private Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java index 2d0aa72611..f3c4197377 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2010 the original author or authors. + * Copyright 2010-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,10 +41,10 @@ public class AstUtils { * @param targetType the type upon which property access is being attempted * @return a list of resolvers that should be tried in order to access the property */ - public static List getPropertyAccessorsToTry(Class targetType, ExpressionState state) { + public static List getPropertyAccessorsToTry(Class targetType, List propertyAccessors) { List specificAccessors = new ArrayList(); List generalAccessors = new ArrayList(); - for (PropertyAccessor resolver : state.getPropertyAccessors()) { + for (PropertyAccessor resolver : propertyAccessors) { Class[] targets = resolver.getSpecificTargetClasses(); if (targets == null) { // generic resolver that says it can be used for any type generalAccessors.add(resolver); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java index c12f736ac6..7dfec0ca33 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,22 +35,19 @@ public class CompoundExpression extends SpelNodeImpl { throw new IllegalStateException("Dont build compound expression less than one entry: "+expressionComponents.length); } } - - /** - * Evalutes a compound expression. This involves evaluating each piece in turn and the return value from each piece - * is the active context object for the subsequent piece. - * @param state the state in which the expression is being evaluated - * @return the final value from the last piece of the compound expression - */ @Override - public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + if (getChildCount()==1) { + return children[0].getValueRef(state); + } TypedValue result = null; SpelNodeImpl nextNode = null; try { nextNode = children[0]; result = nextNode.getValueInternal(state); - for (int i = 1; i < getChildCount(); i++) { + int cc = getChildCount(); + for (int i = 1; i < cc-1; i++) { try { state.pushActiveContextObject(result); nextNode = children[i]; @@ -59,57 +56,39 @@ public class CompoundExpression extends SpelNodeImpl { state.popActiveContextObject(); } } + try { + state.pushActiveContextObject(result); + nextNode = children[cc-1]; + return nextNode.getValueRef(state); + } finally { + state.popActiveContextObject(); + } } catch (SpelEvaluationException ee) { - // Correct the position for the error before rethrowing + // Correct the position for the error before re-throwing ee.setPosition(nextNode.getStartPosition()); throw ee; } - return result; + } + + /** + * Evaluates a compound expression. This involves evaluating each piece in turn and the return value from each piece + * is the active context object for the subsequent piece. + * @param state the state in which the expression is being evaluated + * @return the final value from the last piece of the compound expression + */ + @Override + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + return getValueRef(state).getValue(); } @Override public void setValue(ExpressionState state, Object value) throws EvaluationException { - if (getChildCount() == 1) { - getChild(0).setValue(state, value); - return; - } - TypedValue ctx = children[0].getValueInternal(state); - for (int i = 1; i < getChildCount() - 1; i++) { - try { - state.pushActiveContextObject(ctx); - ctx = children[i].getValueInternal(state); - } finally { - state.popActiveContextObject(); - } - } - try { - state.pushActiveContextObject(ctx); - getChild(getChildCount() - 1).setValue(state, value); - } finally { - state.popActiveContextObject(); - } + getValueRef(state).setValue(value); } @Override public boolean isWritable(ExpressionState state) throws EvaluationException { - if (getChildCount() == 1) { - return getChild(0).isWritable(state); - } - TypedValue ctx = children[0].getValueInternal(state); - for (int i = 1; i < getChildCount() - 1; i++) { - try { - state.pushActiveContextObject(ctx); - ctx = children[i].getValueInternal(state); - } finally { - state.popActiveContextObject(); - } - } - try { - state.pushActiveContextObject(ctx); - return getChild(getChildCount() - 1).isWritable(state); - } finally { - state.popActiveContextObject(); - } + return getValueRef(state).isWritable(); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index ec3e6eb773..1228c0a5d2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypeConverter; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelEvaluationException; @@ -32,8 +33,9 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.ReflectivePropertyAccessor; /** - * An Indexer can index into some proceeding structure to access a particular piece of it. - * Supported structures are: strings/collections (lists/sets)/arrays + * An Indexer can index into some proceeding structure to access a particular + * piece of it. Supported structures are: strings/collections + * (lists/sets)/arrays * * @author Andy Clement * @since 3.0 @@ -42,20 +44,21 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor; // TODO support correct syntax for multidimensional [][][] and not [,,,] public class Indexer extends SpelNodeImpl { - // These fields are used when the indexer is being used as a property read accessor. If the name and - // target type match these cached values then the cachedReadAccessor is used to read the property. - // If they do not match, the correct accessor is discovered and then cached for later use. + // These fields are used when the indexer is being used as a property read accessor. + // If the name and target type match these cached values then the cachedReadAccessor + // is used to read the property. If they do not match, the correct accessor is + // discovered and then cached for later use. private String cachedReadName; private Class cachedReadTargetType; private PropertyAccessor cachedReadAccessor; - // These fields are used when the indexer is being used as a property write accessor. If the name and - // target type match these cached values then the cachedWriteAccessor is used to write the property. - // If they do not match, the correct accessor is discovered and then cached for later use. + // These fields are used when the indexer is being used as a property write accessor. + // If the name and target type match these cached values then the cachedWriteAccessor + // is used to write the property. If they do not match, the correct accessor is + // discovered and then cached for later use. private String cachedWriteName; private Class cachedWriteTargetType; private PropertyAccessor cachedWriteAccessor; - public Indexer(int pos, SpelNodeImpl expr) { super(pos, expr); @@ -63,27 +66,313 @@ public class Indexer extends SpelNodeImpl { @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + return getValueRef(state).getValue(); + } + + @Override + public void setValue(ExpressionState state, Object newValue) throws EvaluationException { + getValueRef(state).setValue(newValue); + } + + @Override + public boolean isWritable(ExpressionState expressionState) + throws SpelEvaluationException { + return true; + } + + class ArrayIndexingValueRef implements ValueRef { + + private TypeConverter typeConverter; + private Object array; + private int idx; + private TypeDescriptor typeDescriptor; + + ArrayIndexingValueRef(TypeConverter typeConverter, Object array, + int idx, TypeDescriptor typeDescriptor) { + this.typeConverter = typeConverter; + this.array = array; + this.idx = idx; + this.typeDescriptor = typeDescriptor; + } + + public TypedValue getValue() { + Object arrayElement = accessArrayElement(array, idx); + return new TypedValue(arrayElement, + typeDescriptor.elementTypeDescriptor(arrayElement)); + } + + public void setValue(Object newValue) { + setArrayElement(typeConverter, array, idx, newValue, typeDescriptor + .getElementTypeDescriptor().getType()); + } + + public boolean isWritable() { + return true; + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + class MapIndexingValueRef implements ValueRef { + + private TypeConverter typeConverter; + private Map map; + private Object key; + private TypeDescriptor mapEntryTypeDescriptor; + + MapIndexingValueRef(TypeConverter typeConverter, Map map, Object key, + TypeDescriptor mapEntryTypeDescriptor) { + this.typeConverter = typeConverter; + this.map = map; + this.key = key; + this.mapEntryTypeDescriptor = mapEntryTypeDescriptor; + } + + public TypedValue getValue() { + Object value = map.get(key); + return new TypedValue(value, + mapEntryTypeDescriptor.getMapValueTypeDescriptor(value)); + } + + public void setValue(Object newValue) { + if (mapEntryTypeDescriptor.getMapValueTypeDescriptor() != null) { + newValue = typeConverter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + mapEntryTypeDescriptor.getMapValueTypeDescriptor()); + } + map.put(key, newValue); + } + + public boolean isWritable() { + return true; + } + + } + + class PropertyIndexingValueRef implements ValueRef { + + private Object targetObject; + private String name; + private EvaluationContext eContext; + private TypeDescriptor td; + + public PropertyIndexingValueRef(Object targetObject, String value, + EvaluationContext evaluationContext, + TypeDescriptor targetObjectTypeDescriptor) { + this.targetObject = targetObject; + this.name = value; + this.eContext = evaluationContext; + this.td = targetObjectTypeDescriptor; + } + + public TypedValue getValue() { + Class targetObjectRuntimeClass = getObjectClass(targetObject); + + try { + if (cachedReadName != null + && cachedReadName.equals(name) + && cachedReadTargetType != null + && cachedReadTargetType + .equals(targetObjectRuntimeClass)) { + // it is OK to use the cached accessor + return cachedReadAccessor + .read(eContext, targetObject, name); + } + + List 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 accessorsToTry = AstUtils + .getPropertyAccessorsToTry(contextObjectClass, + eContext.getPropertyAccessors()); + if (accessorsToTry != null) { + for (PropertyAccessor accessor : accessorsToTry) { + if (accessor.canWrite(eContext, targetObject, name)) { + cachedWriteName = name; + cachedWriteTargetType = contextObjectClass; + cachedWriteAccessor = accessor; + accessor.write(eContext, targetObject, name, + newValue); + return; + } + } + } + } catch (AccessException ae) { + throw new SpelEvaluationException(getStartPosition(), ae, + SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE, name, + ae.getMessage()); + } + } + + public boolean isWritable() { + return true; + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + class CollectionIndexingValueRef implements ValueRef { + + private TypeConverter typeConverter; + private Collection collection; + private int index; + private TypeDescriptor collectionEntryTypeDescriptor; + private boolean growCollection; + + CollectionIndexingValueRef(Collection collection, int index, + TypeDescriptor collectionEntryTypeDescriptor, TypeConverter typeConverter, boolean growCollection) { + this.typeConverter = typeConverter; + this.growCollection = growCollection; + this.collection = collection; + this.index = index; + this.collectionEntryTypeDescriptor = collectionEntryTypeDescriptor; + } + + public TypedValue getValue() { + if (index >= collection.size()) { + if (growCollection) { + growCollection(collectionEntryTypeDescriptor,index,collection); + } else { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, + collection.size(), index); + } + } + int pos = 0; + for (Object o : collection) { + if (pos == index) { + return new TypedValue(o, + collectionEntryTypeDescriptor + .elementTypeDescriptor(o)); + } + pos++; + } + throw new IllegalStateException(); + } + + public void setValue(Object newValue) { + if (index >= collection.size()) { + if (growCollection) { + growCollection(collectionEntryTypeDescriptor, index, collection); + } else { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, + collection.size(), index); + } + } + if (collection instanceof List) { + List list = (List) collection; + if (collectionEntryTypeDescriptor.getElementTypeDescriptor() != null) { + newValue = typeConverter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + collectionEntryTypeDescriptor + .getElementTypeDescriptor()); + } + list.set(index, newValue); + return; + } else { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + collectionEntryTypeDescriptor.toString()); + } + } + + public boolean isWritable() { + return true; + } + } + + class StringIndexingLValue implements ValueRef { + + private String target; + private int index; + private TypeDescriptor td; + + public StringIndexingLValue(String target, int index, TypeDescriptor td) { + this.target = target; + this.index = index; + this.td = td; + } + + public TypedValue getValue() { + if (index >= target.length()) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, + target.length(), index); + } + return new TypedValue(String.valueOf(target.charAt(index))); + } + + public void setValue(Object newValue) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, td.toString()); + } + + public boolean isWritable() { + return true; + } + + } + + @Override + protected ValueRef getValueRef(ExpressionState state) + throws EvaluationException { TypedValue context = state.getActiveContextObject(); Object targetObject = context.getValue(); TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor(); TypedValue indexValue = null; Object index = null; - - // This first part of the if clause prevents a 'double dereference' of the property (SPR-5847) + + // This first part of the if clause prevents a 'double dereference' of + // the property (SPR-5847) if (targetObject instanceof Map && (children[0] instanceof PropertyOrFieldReference)) { - PropertyOrFieldReference reference = (PropertyOrFieldReference)children[0]; + PropertyOrFieldReference reference = (PropertyOrFieldReference) children[0]; index = reference.getName(); indexValue = new TypedValue(index); - } - else { - // In case the map key is unqualified, we want it evaluated against the root object so - // temporarily push that on whilst evaluating the key + } else { + // In case the map key is unqualified, we want it evaluated against + // the root object so temporarily push that on whilst evaluating the key try { state.pushActiveContextObject(state.getRootContextObject()); indexValue = children[0].getValueInternal(state); index = indexValue.getValue(); - } - finally { + } finally { state.popActiveContextObject(); } } @@ -92,207 +381,83 @@ public class Indexer extends SpelNodeImpl { if (targetObject instanceof Map) { Object key = index; if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) { - key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); + key = state.convertValue(key, + targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); } - Object value = ((Map) targetObject).get(key); - return new TypedValue(value, targetObjectTypeDescriptor.getMapValueTypeDescriptor(value)); + return new MapIndexingValueRef(state.getTypeConverter(), + (Map) targetObject, key, targetObjectTypeDescriptor); } - + if (targetObject == null) { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); } - - // if the object is something that looks indexable by an integer, attempt to treat the index value as a number - if (targetObject instanceof Collection || targetObject.getClass().isArray() || targetObject instanceof String) { - int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); + + // if the object is something that looks indexable by an integer, + // attempt to treat the index value as a number + if (targetObject instanceof Collection + || targetObject.getClass().isArray() + || targetObject instanceof String) { + int idx = (Integer) state.convertValue(index, + TypeDescriptor.valueOf(Integer.class)); if (targetObject.getClass().isArray()) { - Object arrayElement = accessArrayElement(targetObject, idx); - return new TypedValue(arrayElement, targetObjectTypeDescriptor.elementTypeDescriptor(arrayElement)); + return new ArrayIndexingValueRef(state.getTypeConverter(), + targetObject, idx, targetObjectTypeDescriptor); } else if (targetObject instanceof Collection) { - Collection c = (Collection) targetObject; - if (idx >= c.size()) { - if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); - } - } - int pos = 0; - for (Object o : c) { - if (pos == idx) { - return new TypedValue(o, targetObjectTypeDescriptor.elementTypeDescriptor(o)); - } - pos++; - } + return new CollectionIndexingValueRef( + (Collection) targetObject, idx, + targetObjectTypeDescriptor,state.getTypeConverter(), + state.getConfiguration().isAutoGrowCollections()); } else if (targetObject instanceof String) { - String ctxString = (String) targetObject; - if (idx >= ctxString.length()) { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.STRING_INDEX_OUT_OF_BOUNDS, ctxString.length(), idx); - } - return new TypedValue(String.valueOf(ctxString.charAt(idx))); + return new StringIndexingLValue((String) targetObject, idx, + targetObjectTypeDescriptor); } } - + // Try and treat the index value as a property of the context object - // TODO could call the conversion service to convert the value to a String - if (indexValue.getTypeDescriptor().getType()==String.class) { - Class targetObjectRuntimeClass = getObjectClass(targetObject); - String name = (String)indexValue.getValue(); - EvaluationContext eContext = state.getEvaluationContext(); - - try { - if (cachedReadName!=null && cachedReadName.equals(name) && cachedReadTargetType!=null && cachedReadTargetType.equals(targetObjectRuntimeClass)) { - // it is OK to use the cached accessor - return cachedReadAccessor.read(eContext, targetObject, name); - } - - List accessorsToTry = AstUtils.getPropertyAccessorsToTry(targetObjectRuntimeClass, state); - - if (accessorsToTry != null) { - for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canRead(eContext, targetObject, name)) { - if (accessor instanceof ReflectivePropertyAccessor) { - accessor = ((ReflectivePropertyAccessor)accessor).createOptimalAccessor(eContext, targetObject, name); - } - this.cachedReadAccessor = accessor; - this.cachedReadName = name; - this.cachedReadTargetType = targetObjectRuntimeClass; - return accessor.read(eContext, targetObject, name); - } - } - } - } catch (AccessException e) { - throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString()); - } + // TODO could call the conversion service to convert the value to a + // String + if (indexValue.getTypeDescriptor().getType() == String.class) { + return new PropertyIndexingValueRef(targetObject, + (String) indexValue.getValue(), + state.getEvaluationContext(), targetObjectTypeDescriptor); } - - throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString()); + + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, + targetObjectTypeDescriptor.toString()); } - - @Override - public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException { - return true; - } - - @SuppressWarnings("unchecked") - @Override - public void setValue(ExpressionState state, Object newValue) throws EvaluationException { - TypedValue contextObject = state.getActiveContextObject(); - Object targetObject = contextObject.getValue(); - TypeDescriptor targetObjectTypeDescriptor = contextObject.getTypeDescriptor(); - TypedValue index = children[0].getValueInternal(state); - if (targetObject == null) { - throw new SpelEvaluationException(SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE); - } - // Indexing into a Map - if (targetObject instanceof Map) { - Map map = (Map) targetObject; - Object key = index.getValue(); - if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) { - key = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); - } - if (targetObjectTypeDescriptor.getMapValueTypeDescriptor() != null) { - newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueTypeDescriptor()); - } - map.put(key, newValue); - return; - } - - if (targetObjectTypeDescriptor.isArray()) { - int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); - setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementTypeDescriptor().getType()); - return; - } - else if (targetObject instanceof Collection) { - int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); - Collection c = (Collection) targetObject; - if (idx >= c.size()) { - if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); - } - } - if (targetObject instanceof List) { - List list = (List) targetObject; - if (targetObjectTypeDescriptor.getElementTypeDescriptor() != null) { - newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementTypeDescriptor()); - } - list.set(idx, newValue); - return; - } - else { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString()); - } - } - - // Try and treat the index value as a property of the context object - // TODO could call the conversion service to convert the value to a String - if (index.getTypeDescriptor().getType() == String.class) { - Class contextObjectClass = getObjectClass(contextObject.getValue()); - String name = (String)index.getValue(); - EvaluationContext eContext = state.getEvaluationContext(); - try { - if (cachedWriteName!=null && cachedWriteName.equals(name) && cachedWriteTargetType!=null && cachedWriteTargetType.equals(contextObjectClass)) { - // it is OK to use the cached accessor - cachedWriteAccessor.write(eContext, targetObject, name,newValue); - return; - } - - List accessorsToTry = AstUtils.getPropertyAccessorsToTry(contextObjectClass, state); - if (accessorsToTry != null) { - for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canWrite(eContext, contextObject.getValue(), name)) { - this.cachedWriteName = name; - this.cachedWriteTargetType = contextObjectClass; - this.cachedWriteAccessor = accessor; - accessor.write(eContext, contextObject.getValue(), name, newValue); - return; - } - } - } - } catch (AccessException ae) { - throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE, - name, ae.getMessage()); - } - - } - - throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString()); - } - /** - * Attempt to grow the specified collection so that the specified index is valid. - * - * @param state the expression state + * Attempt to grow the specified collection so that the specified index is + * valid. + * * @param elementType the type of the elements in the collection * @param index the index into the collection that needs to be valid * @param collection the collection to grow with elements - * @return true if collection growing succeeded, otherwise false */ - @SuppressWarnings("unchecked") - private boolean growCollection(ExpressionState state, TypeDescriptor targetType, int index, - Collection collection) { - if (state.getConfiguration().isAutoGrowCollections()) { - if (targetType.getElementTypeDescriptor() == null) { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); - } - TypeDescriptor elementType = targetType.getElementTypeDescriptor(); - Object newCollectionElement = null; - try { - int newElements = index - collection.size(); - while (newElements>0) { - collection.add(elementType.getType().newInstance()); - newElements--; - } - newCollectionElement = elementType.getType().newInstance(); - } - catch (Exception ex) { - throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); - } - collection.add(newCollectionElement); - return true; + private void growCollection(TypeDescriptor targetType, int index, Collection collection) { + if (targetType.getElementTypeDescriptor() == null) { + throw new SpelEvaluationException( + getStartPosition(), + SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); } - return false; + TypeDescriptor elementType = targetType.getElementTypeDescriptor(); + Object newCollectionElement = null; + try { + int newElements = index - collection.size(); + while (newElements > 0) { + collection.add(elementType.getType().newInstance()); + newElements--; + } + newCollectionElement = elementType.getType().newInstance(); + } catch (Exception ex) { + throw new SpelEvaluationException(getStartPosition(), ex, + SpelMessage.UNABLE_TO_GROW_COLLECTION); + } + collection.add(newCollectionElement); } - + @Override public String toStringAST() { StringBuilder sb = new StringBuilder(); @@ -306,47 +471,66 @@ public class Indexer extends SpelNodeImpl { return sb.toString(); } - private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue, Class clazz) throws EvaluationException { + private void setArrayElement(TypeConverter converter, Object ctx, int idx, + Object newValue, Class clazz) throws EvaluationException { Class arrayComponentType = clazz; if (arrayComponentType == Integer.TYPE) { int[] array = (int[]) ctx; checkAccess(array.length, idx); - array[idx] = (Integer)state.convertValue(newValue, TypeDescriptor.valueOf(Integer.class)); + array[idx] = (Integer) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Integer.class)); } else if (arrayComponentType == Boolean.TYPE) { boolean[] array = (boolean[]) ctx; checkAccess(array.length, idx); - array[idx] = (Boolean)state.convertValue(newValue, TypeDescriptor.valueOf(Boolean.class)); + array[idx] = (Boolean) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Boolean.class)); } else if (arrayComponentType == Character.TYPE) { char[] array = (char[]) ctx; checkAccess(array.length, idx); - array[idx] = (Character)state.convertValue(newValue, TypeDescriptor.valueOf(Character.class)); + array[idx] = (Character) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Character.class)); } else if (arrayComponentType == Long.TYPE) { long[] array = (long[]) ctx; checkAccess(array.length, idx); - array[idx] = (Long)state.convertValue(newValue, TypeDescriptor.valueOf(Long.class)); + array[idx] = (Long) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Long.class)); } else if (arrayComponentType == Short.TYPE) { short[] array = (short[]) ctx; checkAccess(array.length, idx); - array[idx] = (Short)state.convertValue(newValue, TypeDescriptor.valueOf(Short.class)); + array[idx] = (Short) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Short.class)); } else if (arrayComponentType == Double.TYPE) { double[] array = (double[]) ctx; checkAccess(array.length, idx); - array[idx] = (Double)state.convertValue(newValue, TypeDescriptor.valueOf(Double.class)); + array[idx] = (Double) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Double.class)); } else if (arrayComponentType == Float.TYPE) { float[] array = (float[]) ctx; checkAccess(array.length, idx); - array[idx] = (Float)state.convertValue(newValue, TypeDescriptor.valueOf(Float.class)); + array[idx] = (Float) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Float.class)); } else if (arrayComponentType == Byte.TYPE) { byte[] array = (byte[]) ctx; checkAccess(array.length, idx); - array[idx] = (Byte)state.convertValue(newValue, TypeDescriptor.valueOf(Byte.class)); + array[idx] = (Byte) converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(Byte.class)); } else { Object[] array = (Object[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, TypeDescriptor.valueOf(clazz)); - } + array[idx] = converter.convertValue(newValue, + TypeDescriptor.forObject(newValue), + TypeDescriptor.valueOf(clazz)); + } } - + private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationException { Class arrayComponentType = ctx.getClass().getComponentType(); if (arrayComponentType == Integer.TYPE) { @@ -390,8 +574,9 @@ public class Indexer extends SpelNodeImpl { private void checkAccess(int arrayLength, int index) throws SpelEvaluationException { if (index > arrayLength) { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index); + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.ARRAY_INDEX_OUT_OF_BOUNDS, arrayLength, index); } } -} +} \ No newline at end of file diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 22a8ffbbf5..9c74b9c564 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,94 @@ public class MethodReference extends SpelNodeImpl { this.nullSafe = nullSafe; } + class MethodValueRef implements ValueRef { + + private ExpressionState state; + private EvaluationContext evaluationContext; + private Object target; + private Object[] arguments; + + MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, Object object, Object[] arguments) { + this.state = state; + this.evaluationContext = evaluationContext; + this.target = object; + this.arguments = arguments; + } + + public TypedValue getValue() { + MethodExecutor executorToUse = cachedExecutor; + if (executorToUse != null) { + try { + return executorToUse.execute(evaluationContext, target, arguments); + } + catch (AccessException ae) { + // Two reasons this can occur: + // 1. the method invoked actually threw a real exception + // 2. the method invoked was not passed the arguments it expected and has become 'stale' + + // In the first case we should not retry, in the second case we should see if there is a + // better suited method. + + // To determine which situation it is, the AccessException will contain a cause. + // If the cause is an InvocationTargetException, a user exception was thrown inside the method. + // Otherwise the method could not be invoked. + throwSimpleExceptionIfPossible(state, ae); + + // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found + cachedExecutor = null; + } + } + + // either there was no accessor or it no longer existed + executorToUse = findAccessorForMethod(name, getTypes(arguments), target, evaluationContext); + cachedExecutor = executorToUse; + try { + return executorToUse.execute(evaluationContext, target, arguments); + } catch (AccessException ae) { + // Same unwrapping exception handling as above in above catch block + throwSimpleExceptionIfPossible(state, ae); + throw new SpelEvaluationException( getStartPosition(), ae, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, + name, state.getActiveContextObject().getValue().getClass().getName(), ae.getMessage()); + } + } + + public void setValue(Object newValue) { + throw new IllegalAccessError(); + } + + public boolean isWritable() { + return false; + } + + } + + @Override + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + TypedValue currentContext = state.getActiveContextObject(); + Object[] arguments = new Object[getChildCount()]; + for (int i = 0; i < arguments.length; i++) { + // Make the root object the active context again for evaluating the parameter + // expressions + try { + state.pushActiveContextObject(state.getRootContextObject()); + arguments[i] = children[i].getValueInternal(state).getValue(); + } + finally { + state.popActiveContextObject(); + } + } + if (currentContext.getValue() == null) { + if (nullSafe) { + return ValueRef.NullValueRef.instance; + } + else { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED, + FormatHelper.formatMethodForMessage(name, getTypes(arguments))); + } + } + + return new MethodValueRef(state,state.getEvaluationContext(),state.getActiveContextObject().getValue(),arguments); + } @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { @@ -65,7 +153,7 @@ public class MethodReference extends SpelNodeImpl { arguments[i] = children[i].getValueInternal(state).getValue(); } finally { - state.popActiveContextObject(); + state.popActiveContextObject(); } } if (currentContext.getValue() == null) { @@ -88,15 +176,15 @@ public class MethodReference extends SpelNodeImpl { // Two reasons this can occur: // 1. the method invoked actually threw a real exception // 2. the method invoked was not passed the arguments it expected and has become 'stale' - - // In the first case we should not retry, in the second case we should see if there is a + + // In the first case we should not retry, in the second case we should see if there is a // better suited method. - + // To determine which situation it is, the AccessException will contain a cause. // If the cause is an InvocationTargetException, a user exception was thrown inside the method. // Otherwise the method could not be invoked. throwSimpleExceptionIfPossible(state, ae); - + // at this point we know it wasn't a user problem so worth a retry if a better candidate can be found this.cachedExecutor = null; } @@ -118,7 +206,7 @@ public class MethodReference extends SpelNodeImpl { /** - * Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException, + * Decode the AccessException, throwing a lightweight evaluation exception or, if the cause was a RuntimeException, * throw the RuntimeException directly. */ private void throwSimpleExceptionIfPossible(ExpressionState state, AccessException ae) { @@ -132,7 +220,7 @@ public class MethodReference extends SpelNodeImpl { "A problem occurred when trying to execute method '" + this.name + "' on object of type '" + state.getActiveContextObject().getValue().getClass().getName() + "'", rootCause); - } + } } } @@ -157,19 +245,22 @@ public class MethodReference extends SpelNodeImpl { return sb.toString(); } - private MethodExecutor findAccessorForMethod(String name, List argumentTypes, ExpressionState state) + private MethodExecutor findAccessorForMethod(String name, + List argumentTypes, ExpressionState state) throws SpelEvaluationException { + return findAccessorForMethod(name,argumentTypes,state.getActiveContextObject().getValue(),state.getEvaluationContext()); + } - TypedValue context = state.getActiveContextObject(); - Object contextObject = context.getValue(); - EvaluationContext eContext = state.getEvaluationContext(); + private MethodExecutor findAccessorForMethod(String name, + List argumentTypes, Object contextObject, EvaluationContext eContext) + throws SpelEvaluationException { List mResolvers = eContext.getMethodResolvers(); if (mResolvers != null) { for (MethodResolver methodResolver : mResolvers) { try { MethodExecutor cEx = methodResolver.resolve( - state.getEvaluationContext(), contextObject, name, argumentTypes); + eContext, contextObject, name, argumentTypes); if (cEx != null) { return cEx; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java new file mode 100644 index 0000000000..7536ddfa2a --- /dev/null +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDec.java @@ -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; + } + +} diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java new file mode 100644 index 0000000000..eb829e608a --- /dev/null +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpInc.java @@ -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; + } + +} diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index 92f0b49948..2d572e5d83 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,11 @@ public class Projection extends SpelNodeImpl { @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + return getValueRef(state).getValue(); + } + + @Override + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { TypedValue op = state.getActiveContextObject(); Object operand = op.getValue(); @@ -65,7 +70,7 @@ public class Projection extends SpelNodeImpl { if (operand instanceof Map) { Map mapData = (Map) operand; List result = new ArrayList(); - for (Map.Entry entry : mapData.entrySet()) { + for (Map.Entry entry : mapData.entrySet()) { try { state.pushActiveContextObject(new TypedValue(entry)); result.add(this.children[0].getValueInternal(state).getValue()); @@ -74,7 +79,7 @@ public class Projection extends SpelNodeImpl { state.popActiveContextObject(); } } - return new TypedValue(result); // TODO unable to build correct type descriptor + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); // TODO unable to build correct type descriptor } else if (operand instanceof Collection || operandIsArray) { Collection data = (operand instanceof Collection ? (Collection) operand : @@ -104,14 +109,14 @@ public class Projection extends SpelNodeImpl { } Object resultArray = Array.newInstance(arrayElementType, result.size()); System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); - return new TypedValue(resultArray); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); } - return new TypedValue(result); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } else { if (operand==null) { if (this.nullSafe) { - return TypedValue.NULL; + return ValueRef.NullValueRef.instance; } else { throw new SpelEvaluationException(getStartPosition(), diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 6f8b136755..c7f85333a0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { private volatile PropertyAccessor cachedReadAccessor; private volatile PropertyAccessor cachedWriteAccessor; - + public PropertyOrFieldReference(boolean nullSafe, String propertyOrFieldName, int pos) { super(pos); @@ -67,12 +67,52 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } + static class AccessorLValue implements ValueRef { + private PropertyOrFieldReference ref; + private TypedValue contextObject; + private EvaluationContext eContext; + private boolean isAutoGrowNullReferences; + + public AccessorLValue( + PropertyOrFieldReference propertyOrFieldReference, + TypedValue activeContextObject, + EvaluationContext evaluationContext, boolean isAutoGrowNullReferences) { + this.ref = propertyOrFieldReference; + this.contextObject = activeContextObject; + this.eContext =evaluationContext; + this.isAutoGrowNullReferences = isAutoGrowNullReferences; + } + + public TypedValue getValue() { + return ref.getValueInternal(contextObject,eContext,isAutoGrowNullReferences); + } + + public void setValue(Object newValue) { + ref.writeProperty(contextObject,eContext, ref.name, newValue); + } + + public boolean isWritable() { + return true; + } + + } + + @Override + public ValueRef getValueRef(ExpressionState state) throws EvaluationException { + return new AccessorLValue(this,state.getActiveContextObject(),state.getEvaluationContext(),state.getConfiguration().isAutoGrowNullReferences()); + } + @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue result = readProperty(state, this.name); - - // Dynamically create the objects if the user has requested that optional behaviour - if (result.getValue() == null && state.getConfiguration().isAutoGrowNullReferences() && + return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences()); + } + + private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext, boolean isAutoGrowNullReferences) throws EvaluationException { + + TypedValue result = readProperty(contextObject, eContext, this.name); + + // Dynamically create the objects if the user has requested that optional behavior + if (result.getValue() == null && isAutoGrowNullReferences && nextChildIs(Indexer.class, PropertyOrFieldReference.class)) { TypeDescriptor resultDescriptor = result.getTypeDescriptor(); // Creating lists and maps @@ -80,10 +120,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl { // Create a new collection or map ready for the indexer if (resultDescriptor.getType().equals(List.class)) { try { - if (isWritable(state)) { - List newList = ArrayList.class.newInstance(); - writeProperty(state, this.name, newList); - result = readProperty(state, this.name); + if (isWritableProperty(this.name,contextObject,eContext)) { + List newList = ArrayList.class.newInstance(); + writeProperty(contextObject, eContext, this.name, newList); + result = readProperty(contextObject, eContext, this.name); } } catch (InstantiationException ex) { @@ -97,10 +137,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } else { try { - if (isWritable(state)) { - Map newMap = HashMap.class.newInstance(); - writeProperty(state, name, newMap); - result = readProperty(state, this.name); + if (isWritableProperty(this.name,contextObject,eContext)) { + Map newMap = HashMap.class.newInstance(); + writeProperty(contextObject, eContext, name, newMap); + result = readProperty(contextObject, eContext, this.name); } } catch (InstantiationException ex) { @@ -116,10 +156,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl { else { // 'simple' object try { - if (isWritable(state)) { + if (isWritableProperty(this.name,contextObject,eContext)) { Object newObject = result.getTypeDescriptor().getType().newInstance(); - writeProperty(state, name, newObject); - result = readProperty(state, this.name); + writeProperty(contextObject, eContext, name, newObject); + result = readProperty(contextObject, eContext, this.name); } } catch (InstantiationException ex) { @@ -137,12 +177,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl { @Override public void setValue(ExpressionState state, Object newValue) throws SpelEvaluationException { - writeProperty(state, this.name, newValue); + writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue); } @Override public boolean isWritable(ExpressionState state) throws SpelEvaluationException { - return isWritableProperty(this.name, state); + return isWritableProperty(this.name, state.getActiveContextObject(), state.getEvaluationContext()); } @Override @@ -157,8 +197,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { * @return the value of the property * @throws SpelEvaluationException if any problem accessing the property or it cannot be found */ - private TypedValue readProperty(ExpressionState state, String name) throws EvaluationException { - TypedValue contextObject = state.getActiveContextObject(); + private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException { Object targetObject = contextObject.getValue(); if (targetObject == null && this.nullSafe) { @@ -168,7 +207,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { PropertyAccessor accessorToUse = this.cachedReadAccessor; if (accessorToUse != null) { try { - return accessorToUse.read(state.getEvaluationContext(), contextObject.getValue(), name); + return accessorToUse.read(eContext, contextObject.getValue(), name); } catch (AccessException ae) { // this is OK - it may have gone stale due to a class change, @@ -178,8 +217,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } Class contextObjectClass = getObjectClass(contextObject.getValue()); - List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state); - EvaluationContext eContext = state.getEvaluationContext(); + List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors()); // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then // get the accessor and use it. If they are not cacheable but report they can read the property @@ -210,9 +248,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } } - private void writeProperty(ExpressionState state, String name, Object newValue) throws SpelEvaluationException { - TypedValue contextObject = state.getActiveContextObject(); - EvaluationContext eContext = state.getEvaluationContext(); + private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException { if (contextObject.getValue() == null && nullSafe) { return; @@ -220,8 +256,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl { PropertyAccessor accessorToUse = this.cachedWriteAccessor; if (accessorToUse != null) { - try { - accessorToUse.write(state.getEvaluationContext(), contextObject.getValue(), name, newValue); + try { + accessorToUse.write(eContext, contextObject.getValue(), name, newValue); return; } catch (AccessException ae) { @@ -233,7 +269,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { Class contextObjectClass = getObjectClass(contextObject.getValue()); - List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state); + List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors()); if (accessorsToTry != null) { try { for (PropertyAccessor accessor : accessorsToTry) { @@ -258,15 +294,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } } - public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException { - Object contextObject = state.getActiveContextObject().getValue(); + public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext eContext) throws SpelEvaluationException { + Object contextObjectValue = contextObject.getValue(); // TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor(); - EvaluationContext eContext = state.getEvaluationContext(); - List resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject), state); + List resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObjectValue), eContext.getPropertyAccessors()); if (resolversToTry != null) { for (PropertyAccessor pfResolver : resolversToTry) { try { - if (pfResolver.canWrite(eContext, contextObject, name)) { + if (pfResolver.canWrite(eContext, contextObjectValue, name)) { return true; } } @@ -289,10 +324,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl { * @param targetType the type upon which property access is being attempted * @return a list of resolvers that should be tried in order to access the property */ - private List getPropertyAccessorsToTry(Class targetType, ExpressionState state) { + private List getPropertyAccessorsToTry(Class targetType, List propertyAccessors) { List specificAccessors = new ArrayList(); List generalAccessors = new ArrayList(); - for (PropertyAccessor resolver : state.getPropertyAccessors()) { + for (PropertyAccessor resolver : propertyAccessors) { Class[] targets = resolver.getSpecificTargetClasses(); if (targets == null) { // generic resolver that says it can be used for any type generalAccessors.add(resolver); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index c20442e1d2..7c599c6605 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,19 +59,23 @@ public class Selection extends SpelNodeImpl { this.variant = variant; } - @SuppressWarnings("unchecked") @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + return getValueRef(state).getValue(); + } + + @Override + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { TypedValue op = state.getActiveContextObject(); Object operand = op.getValue(); - + SpelNodeImpl selectionCriteria = children[0]; if (operand instanceof Map) { Map mapdata = (Map) operand; // TODO don't lose generic info for the new map Map result = new HashMap(); Object lastKey = null; - for (Map.Entry entry : mapdata.entrySet()) { + for (Map.Entry entry : mapdata.entrySet()) { try { TypedValue kvpair = new TypedValue(entry); state.pushActiveContextObject(kvpair); @@ -80,7 +84,7 @@ public class Selection extends SpelNodeImpl { if (((Boolean) o).booleanValue() == true) { if (variant == FIRST) { result.put(entry.getKey(),entry.getValue()); - return new TypedValue(result); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } result.put(entry.getKey(),entry.getValue()); lastKey = entry.getKey(); @@ -94,15 +98,15 @@ public class Selection extends SpelNodeImpl { } } if ((variant == FIRST || variant == LAST) && result.size() == 0) { - return new TypedValue(null); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(null),this); } if (variant == LAST) { - Map resultMap = new HashMap(); + Map resultMap = new HashMap(); Object lastValue = result.get(lastKey); resultMap.put(lastKey,lastValue); - return new TypedValue(resultMap); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultMap),this); } - return new TypedValue(result); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) { List data = new ArrayList(); Collection c = (operand instanceof Collection) ? @@ -118,7 +122,7 @@ public class Selection extends SpelNodeImpl { if (o instanceof Boolean) { if (((Boolean) o).booleanValue() == true) { if (variant == FIRST) { - return new TypedValue(element); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(element),this); } result.add(element); } @@ -133,24 +137,24 @@ public class Selection extends SpelNodeImpl { } } if ((variant == FIRST || variant == LAST) && result.size() == 0) { - return TypedValue.NULL; + return ValueRef.NullValueRef.instance; } if (variant == LAST) { - return new TypedValue(result.get(result.size() - 1)); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this); } if (operand instanceof Collection) { - return new TypedValue(result); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); } else { Class elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType()); Object resultArray = Array.newInstance(elementType, result.size()); System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); - return new TypedValue(resultArray); + return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); } } else { if (operand==null) { if (nullSafe) { - return TypedValue.NULL; + return ValueRef.NullValueRef.instance; } else { throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, "null"); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 8cfadd25bb..159f732ed4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -154,6 +154,10 @@ public abstract class SpelNodeImpl implements SpelNode { public int getEndPosition() { return (pos&0xffff); - } + } + + protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { + throw new SpelEvaluationException(pos,SpelMessage.NOT_ASSIGNABLE,toStringAST()); + } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ValueRef.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ValueRef.java new file mode 100644 index 0000000000..a373ed3868 --- /dev/null +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ValueRef.java @@ -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(); +} diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java index 87e7244169..99aec584f0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.expression.spel.ast; +import org.springframework.expression.EvaluationContext; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelEvaluationException; @@ -41,6 +42,47 @@ public class VariableReference extends SpelNodeImpl { } + class VariableRef implements ValueRef { + + private String name; + private TypedValue value; + private EvaluationContext eContext; + + public VariableRef(String name, TypedValue value, + EvaluationContext evaluationContext) { + this.name = name; + this.value = value; + this.eContext = evaluationContext; + } + + public TypedValue getValue() { + return value; + } + + public void setValue(Object newValue) { + eContext.setVariable(name, newValue); + } + + public boolean isWritable() { + return true; + } + + } + + + @Override + public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException { + if (this.name.equals(THIS)) { + return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(),this); + } + if (this.name.equals(ROOT)) { + return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(),this); + } + TypedValue result = state.lookupVariable(this.name); + // a null value will mean either the value was null or the variable was not found + return new VariableRef(this.name,result,state.getEvaluationContext()); + } + @Override public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException { if (this.name.equals(THIS)) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index a62477f611..dde96b33aa 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -27,45 +27,7 @@ import org.springframework.expression.spel.InternalParseException; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.SpelParseException; import org.springframework.expression.spel.SpelParserConfiguration; -import org.springframework.expression.spel.ast.Assign; -import org.springframework.expression.spel.ast.BeanReference; -import org.springframework.expression.spel.ast.BooleanLiteral; -import org.springframework.expression.spel.ast.CompoundExpression; -import org.springframework.expression.spel.ast.ConstructorReference; -import org.springframework.expression.spel.ast.Elvis; -import org.springframework.expression.spel.ast.FunctionReference; -import org.springframework.expression.spel.ast.Identifier; -import org.springframework.expression.spel.ast.Indexer; -import org.springframework.expression.spel.ast.InlineList; -import org.springframework.expression.spel.ast.Literal; -import org.springframework.expression.spel.ast.MethodReference; -import org.springframework.expression.spel.ast.NullLiteral; -import org.springframework.expression.spel.ast.OpAnd; -import org.springframework.expression.spel.ast.OpDivide; -import org.springframework.expression.spel.ast.OpEQ; -import org.springframework.expression.spel.ast.OpGE; -import org.springframework.expression.spel.ast.OpGT; -import org.springframework.expression.spel.ast.OpLE; -import org.springframework.expression.spel.ast.OpLT; -import org.springframework.expression.spel.ast.OpMinus; -import org.springframework.expression.spel.ast.OpModulus; -import org.springframework.expression.spel.ast.OpMultiply; -import org.springframework.expression.spel.ast.OpNE; -import org.springframework.expression.spel.ast.OpOr; -import org.springframework.expression.spel.ast.OpPlus; -import org.springframework.expression.spel.ast.OperatorInstanceof; -import org.springframework.expression.spel.ast.OperatorMatches; -import org.springframework.expression.spel.ast.OperatorNot; -import org.springframework.expression.spel.ast.OperatorPower; -import org.springframework.expression.spel.ast.Projection; -import org.springframework.expression.spel.ast.PropertyOrFieldReference; -import org.springframework.expression.spel.ast.QualifiedIdentifier; -import org.springframework.expression.spel.ast.Selection; -import org.springframework.expression.spel.ast.SpelNodeImpl; -import org.springframework.expression.spel.ast.StringLiteral; -import org.springframework.expression.spel.ast.Ternary; -import org.springframework.expression.spel.ast.TypeReference; -import org.springframework.expression.spel.ast.VariableReference; +import org.springframework.expression.spel.ast.*; import org.springframework.util.Assert; /** @@ -231,14 +193,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { //sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*; private SpelNodeImpl eatSumExpression() { SpelNodeImpl expr = eatProductExpression(); - while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) { - Token t = nextToken();//consume PLUS or MINUS + while (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.INC)) { + Token t = nextToken();//consume PLUS or MINUS or INC SpelNodeImpl rhExpr = eatProductExpression(); checkRightOperand(t,rhExpr); if (t.kind==TokenKind.PLUS) { expr = new OpPlus(toPos(t),expr,rhExpr); - } else { - Assert.isTrue(t.kind==TokenKind.MINUS); + } else if (t.kind==TokenKind.MINUS) { expr = new OpMinus(toPos(t),expr,rhExpr); } } @@ -247,10 +208,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ; private SpelNodeImpl eatProductExpression() { - SpelNodeImpl expr = eatPowerExpression(); + SpelNodeImpl expr = eatPowerIncDecExpression(); while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) { Token t = nextToken(); // consume STAR/DIV/MOD - SpelNodeImpl rhExpr = eatPowerExpression(); + SpelNodeImpl rhExpr = eatPowerIncDecExpression(); checkRightOperand(t,rhExpr); if (t.kind==TokenKind.STAR) { expr = new OpMultiply(toPos(t),expr,rhExpr); @@ -264,19 +225,26 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { return expr; } - // powerExpr : unaryExpression (POWER^ unaryExpression)? ; - private SpelNodeImpl eatPowerExpression() { + // powerExpr : unaryExpression (POWER^ unaryExpression)? (INC || DEC) ; + private SpelNodeImpl eatPowerIncDecExpression() { SpelNodeImpl expr = eatUnaryExpression(); if (peekToken(TokenKind.POWER)) { Token t = nextToken();//consume POWER SpelNodeImpl rhExpr = eatUnaryExpression(); checkRightOperand(t,rhExpr); return new OperatorPower(toPos(t),expr, rhExpr); + } else if (expr!=null && peekToken(TokenKind.INC,TokenKind.DEC)) { + Token t = nextToken();//consume INC/DEC + if (t.getKind()==TokenKind.INC) { + return new OpInc(toPos(t),true,expr); + } else { + return new OpDec(toPos(t),true,expr); + } } return expr; } - // unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ; + // unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ; private SpelNodeImpl eatUnaryExpression() { if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) { Token t = nextToken(); @@ -289,6 +257,14 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { Assert.isTrue(t.kind==TokenKind.MINUS); return new OpMinus(toPos(t),expr); } + } else if (peekToken(TokenKind.INC,TokenKind.DEC)) { + Token t = nextToken(); + SpelNodeImpl expr = eatUnaryExpression(); + if (t.getKind()==TokenKind.INC) { + return new OpInc(toPos(t),false,expr); + } else { + return new OpDec(toPos(t),false,expr); + } } else { return eatPrimaryExpression(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java index 91f5caa927..6484a98fa2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/TokenKind.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.expression.spel.standard; /** @@ -29,12 +30,12 @@ enum TokenKind { DIV("/"), GE(">="), GT(">"), LE("<="), LT("<"), EQ("=="), NE("!="), MOD("%"), NOT("!"), ASSIGN("="), INSTANCEOF("instanceof"), MATCHES("matches"), BETWEEN("between"), SELECT("?["), POWER("^"), - ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&") + ELVIS("?:"), SAFE_NAVI("?."), BEAN_REF("@"), SYMBOLIC_OR("||"), SYMBOLIC_AND("&&"), INC("++"), DEC("--") ; - + char[] tokenChars; private boolean hasPayload; // is there more to this token than simply the kind - + private TokenKind(String tokenString) { tokenChars = tokenString.toCharArray(); hasPayload = tokenChars.length==0; @@ -43,15 +44,15 @@ enum TokenKind { private TokenKind() { this(""); } - + public String toString() { return this.name()+(tokenChars.length!=0?"("+new String(tokenChars)+")":""); } - + public boolean hasPayload() { return hasPayload; } - + public int getLength() { return tokenChars.length; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java index 0654fbe3eb..44e122266b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java @@ -55,13 +55,21 @@ class Tokenizer { } else { switch (ch) { case '+': - pushCharToken(TokenKind.PLUS); + if (isTwoCharToken(TokenKind.INC)) { + pushPairToken(TokenKind.INC); + } else { + pushCharToken(TokenKind.PLUS); + } break; case '_': // the other way to start an identifier lexIdentifier(); break; case '-': - pushCharToken(TokenKind.MINUS); + if (isTwoCharToken(TokenKind.DEC)) { + pushPairToken(TokenKind.DEC); + } else { + pushCharToken(TokenKind.MINUS); + } break; case ':': pushCharToken(TokenKind.COLON); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index b638d2c49b..696c68831d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; +import org.springframework.expression.BeanResolver; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; 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. - * + * * @author Andy Clement * @author Mark Fisher * @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 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(); + 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); + } + + } + + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/InProgressTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/InProgressTests.java index 5b3359e5de..3e6ae17ff4 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/InProgressTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/InProgressTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.expression.spel; import java.util.ArrayList; @@ -22,14 +23,13 @@ import junit.framework.Assert; import org.junit.Test; 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 * documentation. - * + * * Where implementation is missing the tests are commented out. - * + * * @author Andy Clement */ public class InProgressTests extends ExpressionTestCase { @@ -37,7 +37,8 @@ public class InProgressTests extends ExpressionTestCase { @Test public void testRelOperatorsBetween01() { 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 @@ -78,7 +79,6 @@ public class InProgressTests extends ExpressionTestCase { public void testProjection06() throws Exception { SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.![true]"); Assert.assertEquals("'abc'.![true]", expr.toStringAST()); - Assert.assertFalse(expr.isWritable(new StandardEvaluationContext())); } // SELECTION @@ -99,7 +99,6 @@ public class InProgressTests extends ExpressionTestCase { @Test public void testSelection03() { evaluate("mapOfNumbersUpToTen.?[key>5].size()", "5", Integer.class); - // evaluate("listOfNumbersUpToTen.?{#this>5}", "5", ArrayList.class); } @Test @@ -143,284 +142,16 @@ public class InProgressTests extends ExpressionTestCase { public void testSelectionAST() throws Exception { SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.^[true]"); Assert.assertEquals("'abc'.^[true]", expr.toStringAST()); - Assert.assertFalse(expr.isWritable(new StandardEvaluationContext())); expr = (SpelExpression) parser.parseExpression("'abc'.?[true]"); Assert.assertEquals("'abc'.?[true]", expr.toStringAST()); - Assert.assertFalse(expr.isWritable(new StandardEvaluationContext())); expr = (SpelExpression) parser.parseExpression("'abc'.$[true]"); Assert.assertEquals("'abc'.$[true]", expr.toStringAST()); - Assert.assertFalse(expr.isWritable(new StandardEvaluationContext())); } // 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 public void testSetConstruction01() { 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); - // } - }