diff --git a/org.springframework.context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java b/org.springframework.context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java index d8b96ccfbbb..888e0abaec9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java @@ -17,15 +17,18 @@ package org.springframework.context.expression; import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; /** * EL property accessor that knows how to traverse the beans and contextual objects * of a Spring {@link org.springframework.beans.factory.config.BeanExpressionContext}. * * @author Juergen Hoeller + * @author Andy Clement * @since 3.0 */ public class BeanExpressionContextAccessor implements PropertyAccessor { @@ -34,8 +37,8 @@ public class BeanExpressionContextAccessor implements PropertyAccessor { return (((BeanExpressionContext) target).containsObject(name)); } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { - return ((BeanExpressionContext) target).getObject(name); + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + return new TypedValue(((BeanExpressionContext) target).getObject(name)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { diff --git a/org.springframework.context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java b/org.springframework.context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java index 973981a8caa..3ebe485dd74 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java @@ -17,15 +17,18 @@ package org.springframework.context.expression; import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; /** * EL property accessor that knows how to traverse the beans of a * Spring {@link org.springframework.beans.factory.BeanFactory}. * * @author Juergen Hoeller + * @author Andy Clement * @since 3.0 */ public class BeanFactoryAccessor implements PropertyAccessor { @@ -34,8 +37,8 @@ public class BeanFactoryAccessor implements PropertyAccessor { return (((BeanFactory) target).containsBean(name)); } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { - return ((BeanFactory) target).getBean(name); + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + return new TypedValue(((BeanFactory) target).getBean(name)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { diff --git a/org.springframework.context/src/main/java/org/springframework/context/expression/MapAccessor.java b/org.springframework.context/src/main/java/org/springframework/context/expression/MapAccessor.java index 8567bbd1a17..1fbe259b693 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/expression/MapAccessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/expression/MapAccessor.java @@ -21,12 +21,15 @@ import java.util.Map; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ast.CommonTypeDescriptors; /** * EL property accessor that knows how to traverse the keys * of a standard {@link java.util.Map}. * * @author Juergen Hoeller + * @author Andy Clement * @since 3.0 */ public class MapAccessor implements PropertyAccessor { @@ -35,8 +38,8 @@ public class MapAccessor implements PropertyAccessor { return (((Map) target).containsKey(name)); } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { - return ((Map) target).get(name); + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + return new TypedValue(((Map) target).get(name),CommonTypeDescriptors.OBJECT_TYPE_DESCRIPTOR); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java index 091d5bd9dc4..d0094376eed 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/ConstructorExecutor.java @@ -16,6 +16,7 @@ package org.springframework.expression; + // TODO Is the resolver/executor model too pervasive in this package? /** * Executors are built by resolvers and can be cached by the infrastructure to repeat an operation quickly without going @@ -39,6 +40,6 @@ public interface ConstructorExecutor { * @return the new object * @throws AccessException if there is a problem executing the command or the CommandExecutor is no longer valid */ - Object execute(EvaluationContext context, Object... arguments) throws AccessException; + TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java index a9e4745e398..4f4482a5215 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -35,7 +35,7 @@ public interface EvaluationContext { /** * @return the root context object against which unqualified properties/methods/etc should be resolved */ - Object getRootObject(); + TypedValue getRootObject(); /** * Set a named variable within this execution context to a specified value. diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java index 55175a3aede..4093d1c766d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/MethodExecutor.java @@ -39,6 +39,6 @@ public interface MethodExecutor { * @return the value returned from execution * @throws AccessException if there is a problem executing the command or the MethodExecutor is no longer valid */ - Object execute(EvaluationContext context, Object target, Object... arguments) throws AccessException; + TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java index e59b84324ad..30bc8f31c4b 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java @@ -58,10 +58,10 @@ public interface PropertyAccessor { * @param context the evaluation context in which the access is being attempted * @param target the target object upon which the property is being accessed * @param name the name of the property being accessed - * @return Object the value of the property + * @return a TypedValue object wrapping the property value read and a type descriptor for it * @throws AccessException if there is any problem accessing the property value */ - Object read(EvaluationContext context, Object target, String name) throws AccessException; + TypedValue read(EvaluationContext context, Object target, String name) throws AccessException; /** * Called to determine if a resolver instance is able to write to a specified property on a specified target object. diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java new file mode 100644 index 00000000000..79d7f6ed1bc --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypedValue.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2009 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; + +import org.springframework.core.convert.TypeDescriptor; + +/** + * Encapsulates an object and a type descriptor that describes it. + * The type descriptor can hold generic information that would + * not be accessible through a simple getClass() call on the object. + * + * @author Andy Clement + * @since 3.0 + */ +public class TypedValue { + + private Object value; + private TypeDescriptor typeDescriptor; + + /** + * Create a TypedValue for a simple object. The type descriptor is inferred + * from the object, so no generic information is preserved. + * @param value the object value + */ + public TypedValue(Object value) { + this.value = value; + this.typeDescriptor = TypeDescriptor.forObject(value); + } + + /** + * Create a TypedValue for a particular value with a particular type descriptor. + * @param value the object value + * @param typeDescriptor a type descriptor describing the type of the value + */ + public TypedValue(Object value, TypeDescriptor typeDescriptor) { + this.value = value; + this.typeDescriptor = typeDescriptor; + } + + public Object getValue() { + return this.value; + } + + public TypeDescriptor getTypeDescriptor() { + return this.typeDescriptor; + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index 5474a880f07..b77825191e9 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -27,6 +27,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.TypedValue; /** * An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other @@ -45,7 +46,7 @@ public class ExpressionState { private final Stack variableScopes = new Stack(); - private final Stack contextObjects = new Stack(); + private final Stack contextObjects = new Stack(); public ExpressionState() { @@ -65,14 +66,18 @@ public class ExpressionState { /** * The active context object is what unqualified references to properties/etc are resolved against. */ - public Object getActiveContextObject() { + public TypedValue getActiveContextObject() { if (this.contextObjects.isEmpty()) { - return this.relatedContext.getRootObject(); + TypedValue rootObject = this.relatedContext.getRootObject(); + if (rootObject == null) { + return new TypedValue(rootObject,TypeDescriptor.NULL_TYPE_DESCRIPTOR); + } + return rootObject; } return this.contextObjects.peek(); } - public void pushActiveContextObject(Object obj) { + public void pushActiveContextObject(TypedValue obj) { this.contextObjects.push(obj); } @@ -80,7 +85,7 @@ public class ExpressionState { this.contextObjects.pop(); } - public Object getRootContextObject() { + public TypedValue getRootContextObject() { return this.relatedContext.getRootObject(); } @@ -88,8 +93,9 @@ public class ExpressionState { this.relatedContext.setVariable(name, value); } - public Object lookupVariable(String name) { - return this.relatedContext.lookupVariable(name); + public TypedValue lookupVariable(String name) { + Object value = this.relatedContext.lookupVariable(name); + return new TypedValue(value,TypeDescriptor.valueOf((value==null?null:value.getClass()))); } public TypeComparator getTypeComparator() { @@ -103,11 +109,15 @@ public class ExpressionState { public T convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { return this.relatedContext.getTypeConverter().convertValue(value, targetTypeDescriptor); } - - public T convertValue(Object value, Class targetType) throws EvaluationException { - return this.relatedContext.getTypeConverter().convertValue(value, targetType); + + public T convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { + return this.relatedContext.getTypeConverter().convertValue(value.getValue(), targetTypeDescriptor); } +// public T convertValue(TypedValue value, Class targetType) throws EvaluationException { +// return this.relatedContext.getTypeConverter().convertValue(value, targetType); +// } + /** * A new scope is entered when a function is invoked */ @@ -137,10 +147,11 @@ public class ExpressionState { return null; } - public Object operate(Operation op, Object left, Object right) throws EvaluationException { + public TypedValue operate(Operation op, Object left, Object right) throws EvaluationException { OperatorOverloader overloader = this.relatedContext.getOperatorOverloader(); if (overloader.overridesOperation(op, left, right)) { - return overloader.operate(op, left, right); + Object returnValue = overloader.operate(op, left, right); + return new TypedValue(returnValue,TypeDescriptor.forObject(returnValue)); } else { throw new SpelException(SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, left, right); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Assign.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Assign.java index 95108dba366..95086aa2ae8 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Assign.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Assign.java @@ -18,8 +18,9 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; -import org.springframework.expression.spel.SpelException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelException; /** * Represents assignment. An alternative to calling setValue() for an expression is to use an assign. @@ -36,9 +37,9 @@ public class Assign extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object newValue = getChild(1).getValueInternal(state); - getChild(0).setValue(state, newValue); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue newValue = getChild(1).getValueInternal(state); + getChild(0).setValue(state, newValue.getValue()); return newValue; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java index 8fc93bdeb6c..6843ee02571 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Represents the literal values TRUE and FALSE. @@ -26,15 +27,15 @@ import org.antlr.runtime.Token; */ public class BooleanLiteral extends Literal { - private final Boolean value; + private final BooleanTypedValue value; public BooleanLiteral(Token payload, boolean value) { super(payload); - this.value = value; + this.value = BooleanTypedValue.forValue(value); } @Override - public Boolean getLiteralValue() { + public BooleanTypedValue getLiteralValue() { return this.value; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CommonTypeDescriptors.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CommonTypeDescriptors.java new file mode 100644 index 00000000000..4505025473b --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CommonTypeDescriptors.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2009 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.core.convert.TypeDescriptor; + +/** + * @author Andy Clement + * @since 3.0 + */ +public interface CommonTypeDescriptors { + // TODO push into TypeDescriptor? + static TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class); + static TypeDescriptor INTEGER_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Integer.class); + static TypeDescriptor CHARACTER_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Character.class); + static TypeDescriptor LONG_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Long.class); + static TypeDescriptor SHORT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Short.class); + static TypeDescriptor BYTE_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Byte.class); + static TypeDescriptor FLOAT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Float.class); + static TypeDescriptor DOUBLE_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Double.class); + static TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); + static TypeDescriptor CLASS_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Class.class); + static TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java index 310061cd486..cf33e21a158 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java @@ -18,8 +18,9 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; -import org.springframework.expression.spel.SpelException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelException; /** * Represents a DOT separated expression sequence, such as 'property1.property2.methodOne()' @@ -40,8 +41,8 @@ public class CompoundExpression extends SpelNodeImpl { * @return the final value from the last piece of the compound expression */ @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object result = null; + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue result = null; SpelNodeImpl nextNode = null; try { nextNode = getChild(0); @@ -69,7 +70,7 @@ public class CompoundExpression extends SpelNodeImpl { getChild(0).setValue(state, value); return; } - Object ctx = getChild(0).getValueInternal(state); + TypedValue ctx = getChild(0).getValueInternal(state); for (int i = 1; i < getChildCount() - 1; i++) { try { state.pushActiveContextObject(ctx); @@ -91,7 +92,7 @@ public class CompoundExpression extends SpelNodeImpl { if (getChildCount() == 1) { return getChild(0).isWritable(state); } - Object ctx = getChild(0).getValueInternal(state); + TypedValue ctx = getChild(0).getValueInternal(state); for (int i = 1; i < getChildCount() - 1; i++) { try { state.pushActiveContextObject(ctx); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index a62ec16aa75..0fbe46ed7ab 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -20,12 +20,13 @@ import java.lang.reflect.Array; import java.util.List; import org.antlr.runtime.Token; - +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.ConstructorExecutor; import org.springframework.expression.ConstructorResolver; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -67,7 +68,7 @@ public class ConstructorReference extends SpelNodeImpl { * Implements getValue() - delegating to the code for building an array or a simple type. */ @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { if (this.isArrayConstructor) { return createArray(state); } @@ -123,14 +124,14 @@ public class ConstructorReference extends SpelNodeImpl { * @return the new array * @throws EvaluationException if there is a problem creating the array */ - private Object createArray(ExpressionState state) throws EvaluationException { - Object intendedArrayType = getChild(0).getValueInternal(state); - if (!(intendedArrayType instanceof String)) { + private TypedValue createArray(ExpressionState state) throws EvaluationException { + TypedValue intendedArrayType = getChild(0).getValueInternal(state); + if (!(intendedArrayType.getValue() instanceof String)) { throw new SpelException(getChild(0).getCharPositionInLine(), SpelMessages.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION, FormatHelper.formatClassNameForMessage(intendedArrayType.getClass())); } - String type = (String) intendedArrayType; + String type = (String) intendedArrayType.getValue(); Class componentType = null; TypeCode arrayTypeCode = TypeCode.forName(type); if (arrayTypeCode == TypeCode.OBJECT) { @@ -140,6 +141,7 @@ public class ConstructorReference extends SpelNodeImpl { } Object newArray = null; + TypeDescriptor newArrayTypeDescriptor = null; if (getChild(1).getChildCount() == 0) { // are the array ranks defined? if (getChildCount() < 3) { @@ -149,13 +151,14 @@ public class ConstructorReference extends SpelNodeImpl { // no array ranks so use the size of the initializer to determine array size int arraySize = getChild(2).getChildCount(); newArray = Array.newInstance(componentType, arraySize); + newArrayTypeDescriptor = TypeDescriptor.valueOf(newArray.getClass()); } else { // Array ranks are specified but is it a single or multiple dimension array? int dimensions = getChild(1).getChildCount(); if (dimensions == 1) { Object o = getChild(1).getValueInternal(state); - int arraySize = state.convertValue(o, Integer.class); + int arraySize = state.convertValue(o, INTEGER_TYPE_DESCRIPTOR); if (getChildCount() == 3) { // Check initializer length matches array size length int initializerLength = getChild(2).getChildCount(); @@ -165,14 +168,16 @@ public class ConstructorReference extends SpelNodeImpl { } } newArray = Array.newInstance(componentType, arraySize); + newArrayTypeDescriptor = TypeDescriptor.valueOf(newArray.getClass()); } else { // Multi-dimensional - hold onto your hat ! int[] dims = new int[dimensions]; for (int d = 0; d < dimensions; d++) { - dims[d] = state.convertValue(getChild(1).getChild(d).getValueInternal(state), Integer.class); + dims[d] = state.convertValue(getChild(1).getChild(d).getValueInternal(state), INTEGER_TYPE_DESCRIPTOR); } newArray = Array.newInstance(componentType, dims); + newArrayTypeDescriptor = TypeDescriptor.valueOf(newArray.getClass()); // TODO check any specified initializer for the multidim array matches } } @@ -195,47 +200,47 @@ public class ConstructorReference extends SpelNodeImpl { } else if (arrayTypeCode == TypeCode.INT) { int[] newIntArray = (int[]) newArray; for (int i = 0; i < newIntArray.length; i++) { - newIntArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Integer.class); + newIntArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), INTEGER_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.BOOLEAN) { boolean[] newBooleanArray = (boolean[]) newArray; for (int i = 0; i < newBooleanArray.length; i++) { - newBooleanArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Boolean.class); + newBooleanArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.CHAR) { char[] newCharArray = (char[]) newArray; for (int i = 0; i < newCharArray.length; i++) { - newCharArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Character.class); + newCharArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), CHARACTER_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.SHORT) { short[] newShortArray = (short[]) newArray; for (int i = 0; i < newShortArray.length; i++) { - newShortArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Short.class); + newShortArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), SHORT_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.LONG) { long[] newLongArray = (long[]) newArray; for (int i = 0; i < newLongArray.length; i++) { - newLongArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Long.class); + newLongArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), LONG_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.FLOAT) { float[] newFloatArray = (float[]) newArray; for (int i = 0; i < newFloatArray.length; i++) { - newFloatArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Float.class); + newFloatArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), FLOAT_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.DOUBLE) { double[] newDoubleArray = (double[]) newArray; for (int i = 0; i < newDoubleArray.length; i++) { - newDoubleArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Double.class); + newDoubleArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), DOUBLE_TYPE_DESCRIPTOR); } } else if (arrayTypeCode == TypeCode.BYTE) { byte[] newByteArray = (byte[]) newArray; for (int i = 0; i < newByteArray.length; i++) { - newByteArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), Byte.class); + newByteArray[i] = state.convertValue(initializer.getChild(i).getValueInternal(state), BYTE_TYPE_DESCRIPTOR); } } } - return newArray; + return new TypedValue(newArray, newArrayTypeDescriptor); } /** @@ -244,13 +249,13 @@ public class ConstructorReference extends SpelNodeImpl { * @return the new object * @throws EvaluationException if there is a problem creating the object */ - private Object createNewInstance(ExpressionState state) throws EvaluationException { + private TypedValue createNewInstance(ExpressionState state) throws EvaluationException { Object[] arguments = new Object[getChildCount() - 1]; Class[] argumentTypes = new Class[getChildCount() - 1]; for (int i = 0; i < arguments.length; i++) { - Object childValue = getChild(i + 1).getValueInternal(state); - arguments[i] = childValue; - argumentTypes[i] = childValue.getClass(); + TypedValue childValue = getChild(i + 1).getValueInternal(state); + arguments[i] = childValue.getValue(); + argumentTypes[i] = childValue.getValue().getClass(); } ConstructorExecutor executorToUse = this.cachedExecutor; @@ -266,7 +271,7 @@ public class ConstructorReference extends SpelNodeImpl { } // either there was no accessor or it no longer exists - String typename = (String) getChild(0).getValueInternal(state); + String typename = (String) getChild(0).getValueInternal(state).getValue(); executorToUse = findExecutorForConstructor(typename, argumentTypes, state); try { return executorToUse.execute(state.getEvaluationContext(), arguments); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Dot.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Dot.java index 4fc79e58aea..c27700015ec 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Dot.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Dot.java @@ -17,8 +17,9 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; -import org.springframework.expression.spel.SpelException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelException; /** * This is used for preserving positional information from the input expression. @@ -39,7 +40,7 @@ public class Dot extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws SpelException { + public TypedValue getValueInternal(ExpressionState state) throws SpelException { // This makes Dot a do-nothing operation, but this is not free in terms of computation return state.getActiveContextObject(); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/EmptySpelNode.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/EmptySpelNode.java index 1ded0a404e9..411a1a6dec8 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/EmptySpelNode.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/EmptySpelNode.java @@ -17,8 +17,9 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; -import org.springframework.expression.spel.SpelException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelException; /** * @author Andy Clement @@ -31,7 +32,7 @@ public class EmptySpelNode extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws SpelException { + public TypedValue getValueInternal(ExpressionState state) throws SpelException { throw new RuntimeException("?"); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index d09bb94f4e7..f59e1925cd1 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -21,9 +21,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.antlr.runtime.Token; - +import org.springframework.core.MethodParameter; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; import org.springframework.expression.TypeConverter; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -53,18 +55,18 @@ public class FunctionReference extends SpelNodeImpl { @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object o = state.lookupVariable(name); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue o = state.lookupVariable(name); if (o == null) { throw new SpelException(SpelMessages.FUNCTION_NOT_DEFINED, name); } // Two possibilities: a lambda function or a Java static method registered as a function - if (!(o instanceof Method)) { + if (!(o.getValue() instanceof Method)) { throw new SpelException(SpelMessages.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, name, o.getClass()); } - return executeFunctionJLRMethod(state, (Method) o); + return executeFunctionJLRMethod(state, (Method) o.getValue()); } /** @@ -75,7 +77,7 @@ public class FunctionReference extends SpelNodeImpl { * @return the return value of the invoked Java method * @throws EvaluationException if there is any problem invoking the method */ - private Object executeFunctionJLRMethod(ExpressionState state, Method m) throws EvaluationException { + private TypedValue executeFunctionJLRMethod(ExpressionState state, Method m) throws EvaluationException { Object[] functionArgs = getArguments(state); if (!m.isVarArgs() && m.getParameterTypes().length != functionArgs.length) { @@ -99,7 +101,7 @@ public class FunctionReference extends SpelNodeImpl { } try { - return m.invoke(m.getClass(), functionArgs); + return new TypedValue(m.invoke(m.getClass(), functionArgs),new TypeDescriptor(new MethodParameter(m,-1))); } catch (IllegalArgumentException e) { throw new SpelException(getCharPositionInLine(), e, SpelMessages.EXCEPTION_DURING_FUNCTION_CALL, name, e .getMessage()); @@ -139,7 +141,7 @@ public class FunctionReference extends SpelNodeImpl { // Compute arguments to the function Object[] arguments = new Object[getChildCount()]; for (int i = 0; i < arguments.length; i++) { - arguments[i] = getChild(i).getValueInternal(state); + arguments[i] = getChild(i).getValueInternal(state).getValue(); } return arguments; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java index 595443afea6..f369f0c7e8e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Identifier.java @@ -17,9 +17,8 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; -import org.springframework.expression.spel.SpelException; /** * @author Andy Clement @@ -27,20 +26,20 @@ import org.springframework.expression.spel.SpelException; */ public class Identifier extends SpelNodeImpl { - private final String id; + private final TypedValue id; public Identifier(Token payload) { super(payload); - this.id = payload.getText(); + this.id = new TypedValue(payload.getText(), STRING_TYPE_DESCRIPTOR); } @Override public String toStringAST() { - return this.id; + return (String)this.id.getValue(); } @Override - public String getValueInternal(ExpressionState state) { + public TypedValue getValueInternal(ExpressionState state) { return this.id; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 1156c7b6d04..c49afcd7fb8 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -23,6 +23,7 @@ import java.util.Map; import org.antlr.runtime.Token; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -43,53 +44,44 @@ public class Indexer extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object ctx = state.getActiveContextObject(); - Object index = getChild(0).getValueInternal(state); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue context = state.getActiveContextObject(); + Object targetObject = context.getValue(); + TypeDescriptor targetObjectTypeDescriptor = context.getTypeDescriptor(); + TypedValue indexValue = getChild(0).getValueInternal(state); + Object index = indexValue.getValue(); // Indexing into a Map - if (ctx instanceof Map) { - return ((Map) ctx).get(index); + if (targetObject instanceof Map) { + Object possiblyConvertedKey = state.convertValue(indexValue,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapKeyType())); + Object o = ((Map) targetObject).get(possiblyConvertedKey); + return new TypedValue(o,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapValueType())); } int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR); - if (ctx.getClass().isArray()) { - return accessArrayElement(ctx, idx); - } else if (ctx instanceof Collection) { - Collection c = (Collection) ctx; + if (targetObjectTypeDescriptor.isArray()) { + return new TypedValue(accessArrayElement(targetObject, idx),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType())); + } else if (targetObjectTypeDescriptor.isCollection()) { + Collection c = (Collection) targetObject; if (idx >= c.size()) { throw new SpelException(SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); } int pos = 0; for (Object o : c) { if (pos == idx) { - return o; + return new TypedValue(o,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType())); } pos++; } - // } else if (ctx instanceof Map) { - // Map c = (Map) ctx; - // // This code would allow a key/value pair to be pulled out by index from a map - // if (idx >= c.size()) { - // throw new ELException(ELMessages.COLLECTION_INDEX_OUT_OF_BOUNDS,c.size(),idx); - // } - // Set keys = c.keySet(); - // int pos = 0; - // for (Object k : keys) { - // if (pos==idx) { - // return new KeyValuePair(k,c.get(k)); - // } - // pos++; - // } - } else if (ctx instanceof String) { - String ctxString = (String) ctx; + } else if (targetObject instanceof String) { + String ctxString = (String) targetObject; if (idx >= ctxString.length()) { throw new SpelException(SpelMessages.STRING_INDEX_OUT_OF_BOUNDS, ctxString.length(), idx); } - return String.valueOf(ctxString.charAt(idx)); + return new TypedValue(String.valueOf(ctxString.charAt(idx)),STRING_TYPE_DESCRIPTOR); } - throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, ctx.getClass().getName()); + throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor); } @@ -101,27 +93,38 @@ public class Indexer extends SpelNodeImpl { @SuppressWarnings("unchecked") @Override public void setValue(ExpressionState state, Object newValue) throws EvaluationException { - Object ctx = state.getActiveContextObject(); - Object index = getChild(0).getValueInternal(state); + TypedValue contextObject = state.getActiveContextObject(); + Object targetObject = contextObject.getValue(); + TypeDescriptor targetObjectTypeDescriptor = contextObject.getTypeDescriptor(); + TypedValue index = getChild(0).getValueInternal(state); // Indexing into a Map - if (ctx instanceof Map) { - ((Map) ctx).put(index,newValue); // TODO missing conversion for both index and newValue + if (targetObjectTypeDescriptor.isMap()) { + Map map = (Map)targetObject; + Object possiblyConvertedKey = state.convertValue(index.getValue(),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapKeyType())); + Object possiblyConvertedValue = state.convertValue(newValue,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapValueType())); + map.put(possiblyConvertedKey,possiblyConvertedValue); return; } - int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR); - - if (ctx.getClass().isArray()) { - setArrayElement(state, ctx, idx, newValue); - } else if (ctx instanceof List) { - List c = (List) ctx; + if (targetObjectTypeDescriptor.isArray()) { + int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR); + setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementType()); + } else if (targetObjectTypeDescriptor.isCollection()) { + int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR); + Collection c = (Collection) targetObject; if (idx >= c.size()) { throw new SpelException(SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); } - c.set(idx,newValue); // TODO missing conversion + if (targetObject instanceof List) { + List list = (List)targetObject; + Object possiblyConvertedValue = state.convertValue(newValue,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType())); + list.set(idx,possiblyConvertedValue); + } else { + throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, contextObject.getClass().getName()); + } } else { - throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, ctx.getClass().getName()); + throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, contextObject.getClass().getName()); } } @@ -138,44 +141,44 @@ public class Indexer extends SpelNodeImpl { return sb.toString(); } - private void setArrayElement(ExpressionState state, Object ctx, int idx, Object newValue) throws EvaluationException { - Class arrayComponentType = ctx.getClass().getComponentType(); + private void setArrayElement(ExpressionState state, 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] = state.convertValue(newValue, Integer.class); + array[idx] = state.convertValue(newValue, INTEGER_TYPE_DESCRIPTOR); } else if (arrayComponentType == Boolean.TYPE) { boolean[] array = (boolean[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Boolean.class); + array[idx] = state.convertValue(newValue, BOOLEAN_TYPE_DESCRIPTOR); } else if (arrayComponentType == Character.TYPE) { char[] array = (char[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Character.class); + array[idx] = state.convertValue(newValue, CHARACTER_TYPE_DESCRIPTOR); } else if (arrayComponentType == Long.TYPE) { long[] array = (long[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Long.class); + array[idx] = state.convertValue(newValue, LONG_TYPE_DESCRIPTOR); } else if (arrayComponentType == Short.TYPE) { short[] array = (short[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Short.class); + array[idx] = state.convertValue(newValue, SHORT_TYPE_DESCRIPTOR); } else if (arrayComponentType == Double.TYPE) { double[] array = (double[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Double.class); + array[idx] = state.convertValue(newValue, DOUBLE_TYPE_DESCRIPTOR); } else if (arrayComponentType == Float.TYPE) { float[] array = (float[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Float.class); + array[idx] = state.convertValue(newValue, FLOAT_TYPE_DESCRIPTOR); } else if (arrayComponentType == Byte.TYPE) { byte[] array = (byte[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, Byte.class); + array[idx] = state.convertValue(newValue, BYTE_TYPE_DESCRIPTOR); } else { Object[] array = (Object[]) ctx; checkAccess(array.length, idx); - array[idx] = state.convertValue(newValue, arrayComponentType); + array[idx] = state.convertValue(newValue, TypeDescriptor.valueOf(clazz)); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java index 8ee2f84cf33..2a5a92dbe0d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.TypedValue; /** * Expression language AST node that represents an integer literal. @@ -26,15 +27,15 @@ import org.antlr.runtime.Token; */ public class IntLiteral extends Literal { - private final Integer value; + private final TypedValue value; IntLiteral(Token payload, int value) { super(payload); - this.value = value; + this.value = new TypedValue(value, INTEGER_TYPE_DESCRIPTOR); } @Override - public Integer getLiteralValue() { + public TypedValue getLiteralValue() { return this.value; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Literal.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Literal.java index 9efe1064a7b..792dc4259b8 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Literal.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Literal.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -34,16 +35,16 @@ public abstract class Literal extends SpelNodeImpl { super(payload); } - public abstract Object getLiteralValue(); + public abstract TypedValue getLiteralValue(); @Override - public final Object getValueInternal(ExpressionState state) throws SpelException { + public final TypedValue getValueInternal(ExpressionState state) throws SpelException { return getLiteralValue(); } @Override public String toString() { - return getLiteralValue().toString(); + return getLiteralValue().getValue().toString(); } @Override diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java index 7d4c597fcd7..4aaa70788c5 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.TypedValue; /** * Expression language AST node that represents a long integer literal. @@ -26,15 +27,15 @@ import org.antlr.runtime.Token; */ public class LongLiteral extends Literal { - private final Long value; + private final TypedValue value; LongLiteral(Token payload, long value) { super(payload); - this.value = value; + this.value = new TypedValue(value, LONG_TYPE_DESCRIPTOR); } @Override - public Long getLiteralValue() { + public TypedValue getLiteralValue() { return this.value; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index d82348a2a67..6ea2dc740a4 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -19,12 +19,12 @@ package org.springframework.expression.spel.ast; import java.util.List; import org.antlr.runtime.Token; - import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.MethodExecutor; import org.springframework.expression.MethodResolver; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -48,11 +48,11 @@ public class MethodReference extends SpelNodeImpl { @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object currentContext = state.getActiveContextObject(); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue currentContext = state.getActiveContextObject(); Object[] arguments = new Object[getChildCount()]; for (int i = 0; i < arguments.length; i++) { - arguments[i] = getChild(i).getValueInternal(state); + arguments[i] = getChild(i).getValueInternal(state).getValue(); } if (currentContext == null) { throw new SpelException(getCharPositionInLine(), SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT, @@ -63,7 +63,7 @@ public class MethodReference extends SpelNodeImpl { if (executorToUse != null) { try { return executorToUse.execute( - state.getEvaluationContext(), state.getActiveContextObject(), arguments); + state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments); } catch (AccessException ae) { // this is OK - it may have gone stale due to a class change, @@ -77,7 +77,7 @@ public class MethodReference extends SpelNodeImpl { this.cachedExecutor = executorToUse; try { return executorToUse.execute( - state.getEvaluationContext(), state.getActiveContextObject(), arguments); + state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments); } catch (AccessException ae) { throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_METHOD_INVOCATION, @@ -139,7 +139,8 @@ public class MethodReference extends SpelNodeImpl { protected MethodExecutor findAccessorForMethod(String name, Class[] argumentTypes, ExpressionState state) throws SpelException { - Object contextObject = state.getActiveContextObject(); + TypedValue context = state.getActiveContextObject(); + Object contextObject = context.getValue(); EvaluationContext eContext = state.getEvaluationContext(); if (contextObject == null) { throw new SpelException(SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT, diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java index fea5a7d3a5b..b2f647aa8ab 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java @@ -17,6 +17,8 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.TypedValue; /** * @author Andy Clement @@ -24,13 +26,15 @@ import org.antlr.runtime.Token; */ public class NullLiteral extends Literal { + private static final TypedValue NULL_TYPED_VALUE = new TypedValue(null,TypeDescriptor.NULL_TYPE_DESCRIPTOR); + public NullLiteral(Token payload) { super(payload); } @Override - public String getLiteralValue() { - return null; + public TypedValue getLiteralValue() { + return NULL_TYPED_VALUE; } @Override diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java index e4b83e666e3..5ee84123724 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java @@ -18,8 +18,10 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Represents the boolean AND operation. @@ -39,7 +41,7 @@ public class OperatorAnd extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { boolean leftValue; boolean rightValue; @@ -52,7 +54,7 @@ public class OperatorAnd extends Operator { } if (leftValue == false) { - return false; // no need to evaluate right operand + return BooleanTypedValue.forValue(false); // no need to evaluate right operand } try { @@ -63,7 +65,7 @@ public class OperatorAnd extends Operator { throw ee; } - return /* leftValue && */rightValue; + return /* leftValue && */BooleanTypedValue.forValue(rightValue); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java index cba9549edff..8dd7c7870cf 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java @@ -24,6 +24,7 @@ import org.springframework.expression.TypeComparator; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Represents the between operator. The left operand to between must be a single value and the right operand must be a @@ -52,9 +53,9 @@ public class OperatorBetween extends Operator { * @throws EvaluationException if there is a problem evaluating the expression */ @Override - public Boolean getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (!(right instanceof List) || ((List) right).size() != 2) { throw new SpelException(getRightOperand().getCharPositionInLine(), SpelMessages.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST); @@ -64,7 +65,7 @@ public class OperatorBetween extends Operator { Object high = l.get(1); TypeComparator comparator = state.getTypeComparator(); try { - return (comparator.compare(left, low) >= 0 && comparator.compare(left, high) <= 0); + return BooleanTypedValue.forValue((comparator.compare(left, low) >= 0 && comparator.compare(left, high) <= 0)); } catch (SpelException ex) { ex.setPosition(getCharPositionInLine()); throw ex; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorDivide.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorDivide.java index 40bea4f079e..4521d331d9c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorDivide.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorDivide.java @@ -17,8 +17,10 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** @@ -39,26 +41,22 @@ public class OperatorDivide extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object operandOne = getLeftOperand().getValueInternal(state); - Object operandTwo = getRightOperand().getValueInternal(state); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object operandOne = getLeftOperand().getValueInternal(state).getValue(); + Object operandTwo = getRightOperand().getValueInternal(state).getValue(); if (operandOne instanceof Number && operandTwo instanceof Number) { Number op1 = (Number) operandOne; Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() / op2.doubleValue(); - } - else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() / op2.floatValue(); - } - else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() / op2.longValue(); - } - else { // TODO what about non-int result of the division? - return op1.intValue() / op2.intValue(); + return new TypedValue(op1.doubleValue() / op2.doubleValue(), DOUBLE_TYPE_DESCRIPTOR); + } else if (op1 instanceof Long || op2 instanceof Long) { + return new TypedValue(op1.longValue() / op2.longValue(), LONG_TYPE_DESCRIPTOR); + } else { // TODO what about non-int result of the division? + return new TypedValue(op1.intValue() / op2.intValue(), INTEGER_TYPE_DESCRIPTOR); } } - return state.operate(Operation.DIVIDE, operandOne, operandTwo); + Object result = state.operate(Operation.DIVIDE, operandOne, operandTwo); + return new TypedValue(result,TypeDescriptor.forObject(result)); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorEquality.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorEquality.java index 377c2a5c4a6..c1509159b30 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorEquality.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorEquality.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements equality operator. @@ -38,26 +39,21 @@ public class OperatorEquality extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() == op2.doubleValue(); - } - else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() == op2.floatValue(); - } - else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() == op2.longValue(); - } - else { - return op1.intValue() == op2.intValue(); + return BooleanTypedValue.forValue(op1.doubleValue() == op2.doubleValue()); + } else if (op1 instanceof Long || op2 instanceof Long) { + return BooleanTypedValue.forValue(op1.longValue() == op2.longValue()); + } else { + return BooleanTypedValue.forValue(op1.intValue() == op2.intValue()); } } - return state.getTypeComparator().compare(left, right) == 0; + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) == 0); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThan.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThan.java index ab4eb48ae53..a545dd52159 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThan.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThan.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements greater-than operator. @@ -38,26 +39,26 @@ public class OperatorGreaterThan extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() > op2.doubleValue(); + return BooleanTypedValue.forValue(op1.doubleValue() > op2.doubleValue()); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() > op2.floatValue(); + return BooleanTypedValue.forValue(op1.floatValue() > op2.floatValue()); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() > op2.longValue(); + return BooleanTypedValue.forValue(op1.longValue() > op2.longValue()); } else { - return op1.intValue() > op2.intValue(); + return BooleanTypedValue.forValue(op1.intValue() > op2.intValue()); } } - return state.getTypeComparator().compare(left, right) > 0; + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) > 0); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThanOrEqual.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThanOrEqual.java index 44288ca0409..47de02e4dfc 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThanOrEqual.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorGreaterThanOrEqual.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements greater-than-or-equal operator. @@ -38,26 +38,26 @@ public class OperatorGreaterThanOrEqual extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() >= op2.doubleValue(); + return BooleanTypedValue.forValue(op1.doubleValue() >= op2.doubleValue()); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() >= op2.floatValue(); + return BooleanTypedValue.forValue(op1.floatValue() >= op2.floatValue()); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() >= op2.longValue(); + return BooleanTypedValue.forValue( op1.longValue() >= op2.longValue()); } else { - return op1.intValue() >= op2.intValue(); + return BooleanTypedValue.forValue(op1.intValue() >= op2.intValue()); } } - return state.getTypeComparator().compare(left, right) >= 0; + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) >= 0); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInequality.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInequality.java index 4f3fd165485..be09285fb65 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInequality.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInequality.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements the not-equal operator. @@ -38,26 +39,21 @@ public class OperatorInequality extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() != op2.doubleValue(); - } - else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() != op2.floatValue(); - } - else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() != op2.longValue(); - } - else { - return op1.intValue() != op2.intValue(); + return BooleanTypedValue.forValue(op1.doubleValue() != op2.doubleValue()); + } else if (op1 instanceof Long || op2 instanceof Long) { + return BooleanTypedValue.forValue(op1.longValue() != op2.longValue()); + } else { + return BooleanTypedValue.forValue(op1.intValue() != op2.intValue()); } } - return state.getTypeComparator().compare(left, right) != 0; + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) != 0); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java index 684bb95459f..5c67ae00426 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java @@ -17,11 +17,12 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * The operator 'instanceof' checks if an object is of the class specified in the right hand operand, @@ -49,19 +50,21 @@ public class OperatorInstanceof extends Operator { * @throws EvaluationException if there is a problem evaluating the expression */ @Override - public Boolean getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); - if (left == null) { - return false; // null is not an instanceof anything + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue left = getLeftOperand().getValueInternal(state); + TypedValue right = getRightOperand().getValueInternal(state); + Object leftValue = left.getValue(); + Object rightValue = right.getValue(); + if (leftValue == null) { + return BooleanTypedValue.False; // null is not an instanceof anything } - if (right == null || !(right instanceof Class)) { + if (rightValue == null || !(rightValue instanceof Class)) { throw new SpelException(getRightOperand().getCharPositionInLine(), SpelMessages.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND, - (right == null ? "null" : right.getClass().getName())); + (rightValue == null ? "null" : rightValue.getClass().getName())); } - Class rightClass = (Class) right; - return rightClass.isAssignableFrom(left.getClass()); + Class rightClass = (Class) rightValue; + return BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass())); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThan.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThan.java index cc8725adbc4..d1b760931b8 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThan.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThan.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements the less-than operator. @@ -38,26 +39,26 @@ public class OperatorLessThan extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() < op2.doubleValue(); + return BooleanTypedValue.forValue(op1.doubleValue() < op2.doubleValue()); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() < op2.floatValue(); + return BooleanTypedValue.forValue(op1.floatValue() < op2.floatValue()); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() < op2.longValue(); + return BooleanTypedValue.forValue(op1.longValue() < op2.longValue()); } else { - return op1.intValue() < op2.intValue(); + return BooleanTypedValue.forValue(op1.intValue() < op2.intValue()); } } - return state.getTypeComparator().compare(left, right) < 0; + return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) < 0); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThanOrEqual.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThanOrEqual.java index f9afba8ffc4..aaf2d3333a3 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThanOrEqual.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorLessThanOrEqual.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements the less-than-or-equal operator. @@ -33,26 +34,26 @@ public class OperatorLessThanOrEqual extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object left = getLeftOperand().getValueInternal(state); - Object right = getRightOperand().getValueInternal(state); + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object left = getLeftOperand().getValueInternal(state).getValue(); + Object right = getRightOperand().getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() <= op2.doubleValue(); + return BooleanTypedValue.forValue(op1.doubleValue() <= op2.doubleValue()); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() <= op2.floatValue(); + return BooleanTypedValue.forValue(op1.floatValue() <= op2.floatValue()); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() <= op2.longValue(); + return BooleanTypedValue.forValue(op1.longValue() <= op2.longValue()); } else { - return op1.intValue() <= op2.intValue(); + return BooleanTypedValue.forValue(op1.intValue() <= op2.intValue()); } } - return state.getTypeComparator().compare(left, right) <= 0; + return BooleanTypedValue.forValue( state.getTypeComparator().compare(left, right) <= 0); } @Override diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java index 0739cc68688..c52e9d0d4e3 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java @@ -25,6 +25,7 @@ import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Implements the matches operator. Matches takes two operands. The first is a string and the second is a java regex. It @@ -51,11 +52,11 @@ public class OperatorMatches extends Operator { * @throws EvaluationException if there is a problem evaluating the expression (e.g. the regex is invalid) */ @Override - public Boolean getValueInternal(ExpressionState state) throws EvaluationException { + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); Object left = leftOp.getValue(state, String.class); - Object right = getRightOperand().getValueInternal(state); + Object right = getRightOperand().getValueInternal(state).getValue(); try { if (!(left instanceof String)) { throw new SpelException(leftOp.getCharPositionInLine(), @@ -67,7 +68,7 @@ public class OperatorMatches extends Operator { } Pattern pattern = Pattern.compile((String) right); Matcher matcher = pattern.matcher((String) left); - return matcher.matches(); + return BooleanTypedValue.forValue(matcher.matches()); } catch (PatternSyntaxException pse) { throw new SpelException(rightOp.getCharPositionInLine(), pse, SpelMessages.INVALID_PATTERN, right); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMinus.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMinus.java index f9a14e389dc..01628594b95 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMinus.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMinus.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; 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.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -49,45 +50,45 @@ public class OperatorMinus extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); if (rightOp == null) {// If only one operand, then this is unary minus - Object left = leftOp.getValueInternal(state); + Object left = leftOp.getValueInternal(state).getValue(); if (left instanceof Number) { Number n = (Number) left; if (left instanceof Double) { - return 0 - n.doubleValue(); + return new TypedValue(0 - n.doubleValue(),DOUBLE_TYPE_DESCRIPTOR); } else if (left instanceof Float) { - return 0 - n.floatValue(); + return new TypedValue(0 - n.floatValue(),FLOAT_TYPE_DESCRIPTOR); } else if (left instanceof Long) { - return 0 - n.longValue(); + return new TypedValue(0 - n.longValue(),LONG_TYPE_DESCRIPTOR); } else { - return 0 - n.intValue(); + return new TypedValue(0 - n.intValue(),INTEGER_TYPE_DESCRIPTOR); } } throw new SpelException(SpelMessages.CANNOT_NEGATE_TYPE, left.getClass().getName()); } else { - Object left = leftOp.getValueInternal(state); - Object right = rightOp.getValueInternal(state); + Object left = leftOp.getValueInternal(state).getValue(); + Object right = rightOp.getValueInternal(state).getValue(); if (left instanceof Number && right instanceof Number) { Number op1 = (Number) left; Number op2 = (Number) right; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() - op2.doubleValue(); + return new TypedValue(op1.doubleValue() - op2.doubleValue(),DOUBLE_TYPE_DESCRIPTOR); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() - op2.floatValue(); + return new TypedValue(op1.floatValue() - op2.floatValue(),FLOAT_TYPE_DESCRIPTOR); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() - op2.longValue(); + return new TypedValue(op1.longValue() - op2.longValue(),LONG_TYPE_DESCRIPTOR); } else { - return op1.intValue() - op2.intValue(); + return new TypedValue(op1.intValue() - op2.intValue(),INTEGER_TYPE_DESCRIPTOR); } } return state.operate(Operation.SUBTRACT, left, right); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorModulus.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorModulus.java index c55a75cdcd5..b3e71e064f0 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorModulus.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorModulus.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** @@ -39,23 +40,23 @@ public class OperatorModulus extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object operandOne = getLeftOperand().getValueInternal(state); - Object operandTwo = getRightOperand().getValueInternal(state); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object operandOne = getLeftOperand().getValueInternal(state).getValue(); + Object operandTwo = getRightOperand().getValueInternal(state).getValue(); if (operandOne instanceof Number && operandTwo instanceof Number) { Number op1 = (Number) operandOne; Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() % op2.doubleValue(); + return new TypedValue(op1.doubleValue() % op2.doubleValue(),DOUBLE_TYPE_DESCRIPTOR); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() % op2.floatValue(); + return new TypedValue(op1.floatValue() % op2.floatValue(),FLOAT_TYPE_DESCRIPTOR); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() % op2.longValue(); + return new TypedValue(op1.longValue() % op2.longValue(),LONG_TYPE_DESCRIPTOR); } else { - return op1.intValue() % op2.intValue(); + return new TypedValue(op1.intValue() % op2.intValue(),INTEGER_TYPE_DESCRIPTOR); } } return state.operate(Operation.MODULUS, operandOne, operandTwo); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMultiply.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMultiply.java index 95e32f4de6f..0f9ee0a0f41 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMultiply.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorMultiply.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** @@ -50,23 +51,23 @@ public class OperatorMultiply extends Operator { * */ @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object operandOne = getLeftOperand().getValueInternal(state); - Object operandTwo = getRightOperand().getValueInternal(state); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + Object operandOne = getLeftOperand().getValueInternal(state).getValue(); + Object operandTwo = getRightOperand().getValueInternal(state).getValue(); if (operandOne instanceof Number && operandTwo instanceof Number) { Number op1 = (Number) operandOne; Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() * op2.doubleValue(); + return new TypedValue(op1.doubleValue() * op2.doubleValue(),DOUBLE_TYPE_DESCRIPTOR); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() * op2.floatValue(); + return new TypedValue(op1.floatValue() * op2.floatValue(),FLOAT_TYPE_DESCRIPTOR); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() * op2.longValue(); + return new TypedValue(op1.longValue() * op2.longValue(),LONG_TYPE_DESCRIPTOR); } else { - return op1.intValue() * op2.intValue(); + return new TypedValue(op1.intValue() * op2.intValue(),INTEGER_TYPE_DESCRIPTOR); } } else if (operandOne instanceof String && operandTwo instanceof Integer) { @@ -75,7 +76,7 @@ public class OperatorMultiply extends Operator { for (int i = 0; i < repeats; i++) { result.append(operandOne); } - return result.toString(); + return new TypedValue(result.toString(),STRING_TYPE_DESCRIPTOR); } return state.operate(Operation.MULTIPLY, operandOne, operandTwo); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java index d9942b44649..5c7b33ae1b6 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java @@ -20,6 +20,7 @@ import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Represents a NOT operation. @@ -34,10 +35,10 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { try { boolean value = state.convertValue(getChild(0).getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR); - return !value; + return BooleanTypedValue.forValue(!value); } catch (SpelException see) { see.setPosition(getChild(0).getCharPositionInLine()); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java index 044f1ff6e30..6c56adf337b 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java @@ -20,6 +20,7 @@ import org.antlr.runtime.Token; import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; +import org.springframework.expression.spel.support.BooleanTypedValue; /** * Represents the boolean OR operation. @@ -39,7 +40,7 @@ public class OperatorOr extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { boolean leftValue; boolean rightValue; try { @@ -51,7 +52,7 @@ public class OperatorOr extends Operator { } if (leftValue == true) { - return true; // no need to evaluate right operand + return BooleanTypedValue.True; // no need to evaluate right operand } try { @@ -62,7 +63,7 @@ public class OperatorOr extends Operator { throw see; } - return leftValue || rightValue; + return BooleanTypedValue.forValue(leftValue || rightValue); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorPlus.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorPlus.java index 4c9b5b67164..38111078cad 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorPlus.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorPlus.java @@ -17,9 +17,9 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** @@ -33,46 +33,46 @@ public class OperatorPlus extends Operator { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); if (rightOp == null) { // If only one operand, then this is unary plus - Object operandOne = leftOp.getValueInternal(state); + Object operandOne = leftOp.getValueInternal(state).getValue(); if (operandOne instanceof Number) { - return ((Number) operandOne).intValue(); + return new TypedValue(((Number) operandOne).intValue(),INTEGER_TYPE_DESCRIPTOR); } return state.operate(Operation.ADD, operandOne, null); } else { - Object operandOne = leftOp.getValueInternal(state); - Object operandTwo = rightOp.getValueInternal(state); + Object operandOne = leftOp.getValueInternal(state).getValue(); + Object operandTwo = rightOp.getValueInternal(state).getValue(); if (operandOne instanceof Number && operandTwo instanceof Number) { Number op1 = (Number) operandOne; Number op2 = (Number) operandTwo; if (op1 instanceof Double || op2 instanceof Double) { - return op1.doubleValue() + op2.doubleValue(); + return new TypedValue(op1.doubleValue() + op2.doubleValue(),DOUBLE_TYPE_DESCRIPTOR); } else if (op1 instanceof Float || op2 instanceof Float) { - return op1.floatValue() + op2.floatValue(); + return new TypedValue(op1.floatValue() + op2.floatValue(),FLOAT_TYPE_DESCRIPTOR); } else if (op1 instanceof Long || op2 instanceof Long) { - return op1.longValue() + op2.longValue(); + return new TypedValue(op1.longValue() + op2.longValue(),LONG_TYPE_DESCRIPTOR); } else { // TODO what about overflow? - return op1.intValue() + op2.intValue(); + return new TypedValue(op1.intValue() + op2.intValue(),INTEGER_TYPE_DESCRIPTOR); } } else if (operandOne instanceof String && operandTwo instanceof String) { - return new StringBuilder((String) operandOne).append((String) operandTwo).toString(); + return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString(),STRING_TYPE_DESCRIPTOR); } else if (operandOne instanceof String && operandTwo instanceof Integer) { String l = (String) operandOne; Integer i = (Integer) operandTwo; // implements character + int (ie. a + 1 = b) if (l.length() == 1) { - return Character.toString((char) (l.charAt(0) + i)); + return new TypedValue(Character.toString((char) (l.charAt(0) + i)),STRING_TYPE_DESCRIPTOR); } - return new StringBuilder(l).append(i).toString(); + return new TypedValue(new StringBuilder(l).append(i).toString(),STRING_TYPE_DESCRIPTOR); } return state.operate(Operation.ADD, operandOne, operandTwo); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Placeholder.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Placeholder.java index d67b5058a23..ea04f267483 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Placeholder.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Placeholder.java @@ -17,9 +17,10 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; -import org.springframework.expression.spel.ExpressionState; /** * PlaceHolder nodes are created for tokens that come through from the grammar purely to give us additional positional @@ -35,7 +36,7 @@ public class Placeholder extends SpelNodeImpl { } @Override - public String getValueInternal(ExpressionState state) throws SpelException { + public TypedValue getValueInternal(ExpressionState state) throws SpelException { throw new SpelException(getCharPositionInLine(), SpelMessages.PLACEHOLDER_SHOULD_NEVER_BE_EVALUATED); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index cf7614514d9..d0ff02cb22f 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -22,8 +22,9 @@ import java.util.List; import java.util.Map; import org.antlr.runtime.Token; - +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -43,9 +44,12 @@ public class Projection extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object operand = state.getActiveContextObject(); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue op = state.getActiveContextObject(); + Object operand = op.getValue(); + TypeDescriptor operandTypeDescriptor = op.getTypeDescriptor(); + // When the input is a map, we push a special context object on the stack // before calling the specified operation. This special context object // has two fields 'key' and 'value' that refer to the map entries key @@ -56,21 +60,21 @@ public class Projection extends SpelNodeImpl { List result = new ArrayList(); for (Object k : mapdata.keySet()) { try { - state.pushActiveContextObject(new KeyValuePair(k, mapdata.get(k))); + state.pushActiveContextObject(new TypedValue(new KeyValuePair(k, mapdata.get(k)),TypeDescriptor.valueOf(KeyValuePair.class))); result.add(getChild(0).getValueInternal(state)); } finally { state.popActiveContextObject(); } } - return result; - } else if (operand instanceof Collection) { + return new TypedValue(result,TypeDescriptor.valueOf(Map.class)); // TODO unable to build correct type descriptor + } else if (operand instanceof List) { List data = new ArrayList(); data.addAll((Collection) operand); List result = new ArrayList(); int idx = 0; for (Object element : data) { try { - state.pushActiveContextObject(element); + state.pushActiveContextObject(new TypedValue(element,TypeDescriptor.valueOf(op.getTypeDescriptor().getType()))); state.enterScope("index", idx); result.add(getChild(0).getValueInternal(state)); } finally { @@ -79,7 +83,7 @@ public class Projection extends SpelNodeImpl { } idx++; } - return result; + return new TypedValue(result,op.getTypeDescriptor()); } else { throw new SpelException(SpelMessages.PROJECTION_NOT_SUPPORTED_ON_TYPE, operand.getClass().getName()); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 7cfe1c56a54..48c0f809f9f 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -23,6 +23,7 @@ import org.antlr.runtime.Token; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -49,7 +50,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { @Override - public Object getValueInternal(ExpressionState state) throws SpelException { + public TypedValue getValueInternal(ExpressionState state) throws SpelException { return readProperty(state, this.name); } @@ -75,14 +76,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl { * @return the value of the property * @throws SpelException if any problem accessing the property or it cannot be found */ - private Object readProperty(ExpressionState state, String name) throws SpelException { - Object contextObject = state.getActiveContextObject(); + private TypedValue readProperty(ExpressionState state, String name) throws SpelException { + TypedValue contextObject = state.getActiveContextObject(); EvaluationContext eContext = state.getEvaluationContext(); PropertyAccessor accessorToUse = this.cachedReadAccessor; if (accessorToUse != null) { try { - return accessorToUse.read(state.getEvaluationContext(), contextObject, name); + return accessorToUse.read(state.getEvaluationContext(), contextObject.getValue(), name); } catch (AccessException ae) { // this is OK - it may have gone stale due to a class change, @@ -91,7 +92,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } } - Class contextObjectClass = getObjectClass(contextObject); + Class contextObjectClass = getObjectClass(contextObject.getValue()); List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state); // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then @@ -100,9 +101,9 @@ public class PropertyOrFieldReference extends SpelNodeImpl { if (accessorsToTry != null) { try { for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canRead(eContext, contextObject, name)) { + if (accessor.canRead(eContext, contextObject.getValue(), name)) { this.cachedReadAccessor = accessor; - return accessor.read(eContext, contextObject, name); + return accessor.read(eContext, contextObject.getValue(), name); } } } @@ -115,13 +116,13 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } private void writeProperty(ExpressionState state, String name, Object newValue) throws SpelException { - Object contextObject = state.getActiveContextObject(); + TypedValue contextObject = state.getActiveContextObject(); EvaluationContext eContext = state.getEvaluationContext(); PropertyAccessor accessorToUse = this.cachedWriteAccessor; if (accessorToUse != null) { try { - accessorToUse.write(state.getEvaluationContext(), contextObject, name, newValue); + accessorToUse.write(state.getEvaluationContext(), contextObject.getValue(), name, newValue); return; } catch (AccessException ae) { @@ -131,15 +132,15 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } } - Class contextObjectClass = getObjectClass(contextObject); + Class contextObjectClass = getObjectClass(contextObject.getValue()); List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state); if (accessorsToTry != null) { try { for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canWrite(eContext, contextObject, name)) { + if (accessor.canWrite(eContext, contextObject.getValue(), name)) { this.cachedWriteAccessor = accessor; - accessor.write(eContext, contextObject, name, newValue); + accessor.write(eContext, contextObject.getValue(), name, newValue); return; } } @@ -153,7 +154,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } public boolean isWritableProperty(String name, ExpressionState state) throws SpelException { - Object contextObject = state.getActiveContextObject(); + Object contextObject = state.getActiveContextObject().getValue(); EvaluationContext eContext = state.getEvaluationContext(); if (contextObject == null) { throw new SpelException(SpelMessages.ATTEMPTED_PROPERTY_FIELD_REF_ON_NULL_CONTEXT_OBJECT, name); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java index a15ac078e3b..7a6740a59fe 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/QualifiedIdentifier.java @@ -17,8 +17,8 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; /** @@ -39,7 +39,7 @@ public class QualifiedIdentifier extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { // Cache the concatenation of child identifiers if (this.value == null) { StringBuilder sb = new StringBuilder(); @@ -47,11 +47,11 @@ public class QualifiedIdentifier extends SpelNodeImpl { if (i > 0) { sb.append("."); } - sb.append(getChild(i).getValueInternal(state)); + sb.append(getChild(i).getValueInternal(state).getValue()); } this.value = sb.toString(); } - return this.value; + return new TypedValue(this.value,STRING_TYPE_DESCRIPTOR); } @Override diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java index 17461e2e30d..3a9c36864f8 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.TypedValue; /** * @author Andy Clement @@ -24,15 +25,15 @@ import org.antlr.runtime.Token; */ public class RealLiteral extends Literal { - private final Double value; + private final TypedValue value; public RealLiteral(Token payload) { super(payload); - value = Double.parseDouble(payload.getText()); + value = new TypedValue(Double.parseDouble(payload.getText()),DOUBLE_TYPE_DESCRIPTOR); } @Override - public Double getLiteralValue() { + public TypedValue getLiteralValue() { return this.value; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 94ddce2355f..325b2179f6d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -22,8 +22,9 @@ import java.util.List; import java.util.Map; import org.antlr.runtime.Token; - +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -51,15 +52,17 @@ public class Selection extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { - Object operand = state.getActiveContextObject(); + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue op = state.getActiveContextObject(); + Object operand = op.getValue(); + SpelNodeImpl selectionCriteria = getChild(0); if (operand instanceof Map) { Map mapdata = (Map) operand; List result = new ArrayList(); for (Object k : mapdata.keySet()) { try { - Object kvpair = new KeyValuePair(k, mapdata.get(k)); + TypedValue kvpair = new TypedValue(new KeyValuePair(k, mapdata.get(k)),TypeDescriptor.valueOf(KeyValuePair.class)); state.pushActiveContextObject(kvpair); Object o = selectionCriteria.getValueInternal(state); if (o instanceof Boolean) { @@ -79,10 +82,10 @@ public class Selection extends SpelNodeImpl { return null; } if (variant == LAST) { - return result.get(result.size() - 1); + return new TypedValue(result.get(result.size() - 1),TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType())); } } - return result; + return new TypedValue(result,op.getTypeDescriptor()); } else if (operand instanceof Collection) { List data = new ArrayList(); data.addAll((Collection) operand); @@ -90,13 +93,13 @@ public class Selection extends SpelNodeImpl { int idx = 0; for (Object element : data) { try { - state.pushActiveContextObject(element); + state.pushActiveContextObject(new TypedValue(element,TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType()))); state.enterScope("index", idx); - Object o = selectionCriteria.getValueInternal(state); + Object o = selectionCriteria.getValueInternal(state).getValue(); if (o instanceof Boolean) { if (((Boolean) o).booleanValue() == true) { if (variant == FIRST) - return element; + return new TypedValue(element,TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType())); result.add(element); } } else { @@ -113,9 +116,9 @@ public class Selection extends SpelNodeImpl { return null; } if (variant == LAST) { - return result.get(result.size() - 1); + return new TypedValue(result.get(result.size() - 1),TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType())); } - return result; + return new TypedValue(result,op.getTypeDescriptor()); } else { throw new SpelException(getCharPositionInLine(), SpelMessages.INVALID_TYPE_FOR_SELECTION, (operand == null ? "null" : operand.getClass().getName())); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 2e620cf01cd..4b3c9df5c2c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -20,8 +20,8 @@ import java.io.Serializable; import org.antlr.runtime.Token; import org.antlr.runtime.tree.CommonTree; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.common.ExpressionUtils; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; @@ -36,10 +36,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; * @author Andy Clement * @since 3.0 */ -public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Serializable { - - protected static TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class); - protected static TypeDescriptor INTEGER_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Integer.class); +public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Serializable, CommonTypeDescriptors { /** * The Antlr parser uses this constructor to build SpelNodes. @@ -51,7 +48,7 @@ public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Seria public final Object getValue(ExpressionState expressionState) throws EvaluationException { if (expressionState != null) { - return getValueInternal(expressionState); + return getValueInternal(expressionState).getValue(); } else { return getValue(new ExpressionState(new StandardEvaluationContext())); @@ -88,7 +85,7 @@ public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Seria @SuppressWarnings("unchecked") protected final T getValue(ExpressionState state, Class desiredReturnType) throws EvaluationException { - Object result = getValueInternal(state); + Object result = getValueInternal(state).getValue(); if (result != null && desiredReturnType != null) { Class resultType = result.getClass(); if (desiredReturnType.isAssignableFrom(resultType)) { @@ -105,7 +102,7 @@ public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Seria } - public abstract Object getValueInternal(ExpressionState expressionState) throws EvaluationException; + public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException; public abstract String toStringAST(); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java index edde66c9c3a..89e21659e46 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; +import org.springframework.expression.TypedValue; /** * @author Andy Clement @@ -25,24 +26,24 @@ import org.antlr.runtime.Token; */ public class StringLiteral extends Literal { - private final String value; + private final TypedValue value; public StringLiteral(Token payload) { super(payload); String val = payload.getText(); // TODO should these have been skipped being created by the parser rules? or not? val = val.substring(1, val.length() - 1); - this.value = val.replaceAll("''", "'"); + this.value = new TypedValue(val.replaceAll("''", "'"),STRING_TYPE_DESCRIPTOR); } @Override - public String getLiteralValue() { + public TypedValue getLiteralValue() { return this.value; } @Override public String toString() { - return "'" + getLiteralValue() + "'"; + return "'" + getLiteralValue().getValue() + "'"; } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java index 7e523e51054..b601641571e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java @@ -17,8 +17,8 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; @@ -42,7 +42,7 @@ public class Ternary extends SpelNodeImpl { * executing the chosen alternative */ @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { Boolean value = getChild(0).getValue(state, Boolean.class); try { if (Boolean.TRUE.equals(value)) { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java index 2ac2e22d8e8..7fbafed9249 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java @@ -17,8 +17,8 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - import org.springframework.expression.EvaluationException; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; @@ -34,17 +34,17 @@ public class TypeReference extends SpelNodeImpl { } @Override - public Object getValueInternal(ExpressionState state) throws EvaluationException { + public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { // TODO possible optimization here if we cache the discovered type reference, but can we do that? - String typename = (String) getChild(0).getValueInternal(state); + String typename = (String) getChild(0).getValueInternal(state).getValue(); if (typename.indexOf(".") == -1 && Character.isLowerCase(typename.charAt(0))) { TypeCode tc = TypeCode.forName(typename); if (tc != TypeCode.OBJECT) { // it is a primitive type - return tc.getType(); + return new TypedValue(tc.getType(),CLASS_TYPE_DESCRIPTOR); } } - return state.findType(typename); + return new TypedValue(state.findType(typename),CLASS_TYPE_DESCRIPTOR); } @Override diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java index f40302b70e6..ca35d2b772c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java @@ -17,7 +17,7 @@ package org.springframework.expression.spel.ast; import org.antlr.runtime.Token; - +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; @@ -44,14 +44,14 @@ public class VariableReference extends SpelNodeImpl { @Override - public Object getValueInternal(ExpressionState state) throws SpelException { + public TypedValue getValueInternal(ExpressionState state) throws SpelException { if (this.name.equals(THIS)) { return state.getActiveContextObject(); } if (this.name.equals(ROOT)) { return state.getRootContextObject(); } - Object result = state.lookupVariable(this.name); + TypedValue result = state.lookupVariable(this.name); if (result == null) { throw new SpelException(getCharPositionInLine(), SpelMessages.VARIABLE_NOT_FOUND, this.name); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java new file mode 100644 index 00000000000..75537ca1a44 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/BooleanTypedValue.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2009 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.support; + +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ast.SpelNodeImpl; + +/** + * @author Andy Clement + * @since 3.0 + */ +public class BooleanTypedValue extends TypedValue { + + public static final BooleanTypedValue True = new BooleanTypedValue(true); + public static final BooleanTypedValue False = new BooleanTypedValue(false); + + private BooleanTypedValue(boolean b) { + super(b,SpelNodeImpl.BOOLEAN_TYPE_DESCRIPTOR); + } + + public static BooleanTypedValue forValue(boolean b) { + if (b) { + return True; + } else { + return False; + } + } +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java index 21bda82d59c..9a4ce4008f0 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java @@ -18,9 +18,11 @@ package org.springframework.expression.spel.support; import java.lang.reflect.Constructor; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.ConstructorExecutor; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.TypedValue; /** * A simple ConstructorExecutor implementation that runs a constructor using reflective invocation. @@ -42,7 +44,7 @@ class ReflectiveConstructorExecutor implements ConstructorExecutor { this.argsRequiringConversion = argsRequiringConversion; } - public Object execute(EvaluationContext context, Object... arguments) throws AccessException { + public TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException { try { if (argsRequiringConversion != null && arguments != null) { ReflectionHelper.convertArguments(c.getParameterTypes(), c.isVarArgs(), @@ -54,7 +56,7 @@ class ReflectiveConstructorExecutor implements ConstructorExecutor { if (!c.isAccessible()) { c.setAccessible(true); } - return c.newInstance(arguments); + return new TypedValue(c.newInstance(arguments),TypeDescriptor.valueOf(c.getClass())); } catch (Exception ex) { throw new AccessException("Problem invoking constructor: " + c, ex); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index c64e48a5cfe..3f601dc753e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -18,9 +18,12 @@ package org.springframework.expression.spel.support; import java.lang.reflect.Method; +import org.springframework.core.MethodParameter; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.MethodExecutor; +import org.springframework.expression.TypedValue; import org.springframework.util.ReflectionUtils; /** @@ -42,7 +45,7 @@ class ReflectiveMethodExecutor implements MethodExecutor { } - public Object execute(EvaluationContext context, Object target, Object... arguments) throws AccessException { + public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException { try { if (this.argsRequiringConversion != null && arguments != null) { ReflectionHelper.convertArguments(this.method.getParameterTypes(), this.method.isVarArgs(), @@ -52,7 +55,7 @@ class ReflectiveMethodExecutor implements MethodExecutor { arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.method.getParameterTypes(), arguments); } ReflectionUtils.makeAccessible(this.method); - return this.method.invoke(target, arguments); + return new TypedValue(this.method.invoke(target, arguments), new TypeDescriptor(new MethodParameter(method,-1))); } catch (Exception ex) { throw new AccessException("Problem invoking method: " + this.method, ex); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java index 7f06c4ea136..ae2f1d0578d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java @@ -30,6 +30,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.TypedValue; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -85,7 +86,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor { return false; } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { if (target == null) { return null; } @@ -95,7 +96,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor { if (target instanceof Class) { throw new AccessException("Cannot access length on array class itself"); } - return Array.getLength(target); + return new TypedValue(Array.getLength(target),TypeDescriptor.valueOf(Integer.TYPE)); } CacheKey cacheKey = new CacheKey(type, name); @@ -113,7 +114,8 @@ public class ReflectivePropertyResolver implements PropertyAccessor { if (method != null) { try { ReflectionUtils.makeAccessible(method); - return method.invoke(target); + TypeDescriptor resultTypeDescriptor = new TypeDescriptor(new MethodParameter(method,-1)); + return new TypedValue(method.invoke(target),resultTypeDescriptor); } catch (Exception ex) { throw new AccessException("Unable to access property '" + name + "' through getter", ex); @@ -133,7 +135,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor { if (field != null) { try { ReflectionUtils.makeAccessible(field); - return field.get(target); + return new TypedValue(field.get(target),new TypeDescriptor(field)); } catch (Exception ex) { throw new AccessException("Unable to access field: " + name, ex); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java index 21d3269302d..612a1c40d2e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.ConstructorResolver; import org.springframework.expression.EvaluationContext; import org.springframework.expression.MethodResolver; @@ -30,6 +31,7 @@ import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypeComparator; import org.springframework.expression.TypeConverter; import org.springframework.expression.TypeLocator; +import org.springframework.expression.TypedValue; import org.springframework.util.Assert; /** @@ -43,7 +45,7 @@ import org.springframework.util.Assert; */ public class StandardEvaluationContext implements EvaluationContext { - private Object rootObject; + private TypedValue rootObject; private final Map variables = new HashMap(); @@ -68,10 +70,14 @@ public class StandardEvaluationContext implements EvaluationContext { } public void setRootObject(Object rootObject) { - this.rootObject = rootObject; + this.rootObject = new TypedValue(rootObject,TypeDescriptor.forObject(rootObject)); } - public Object getRootObject() { + public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor) { + this.rootObject = new TypedValue(rootObject,typeDescriptor); + } + + public TypedValue getRootObject() { return this.rootObject; } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java index 51ab26092f0..4a12d0a1947 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java @@ -23,12 +23,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.ParseException; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -235,6 +237,7 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase { private static class FruitColourAccessor implements PropertyAccessor { private static Map propertyMap = new HashMap(); + private static TypeDescriptor mapElementTypeDescriptor = TypeDescriptor.valueOf(Color.class); static { propertyMap.put("banana",Color.yellow); @@ -253,8 +256,8 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase { return propertyMap.containsKey(name); } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { - return propertyMap.get(name); + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + return new TypedValue(propertyMap.get(name),mapElementTypeDescriptor); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { @@ -292,8 +295,8 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase { return propertyMap.containsKey(name); } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { - return propertyMap.get(name); + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + return new TypedValue(propertyMap.get(name),TypeDescriptor.valueOf(Color.class)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java index ae97fa3ede4..7ed32854487 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java @@ -22,7 +22,9 @@ import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser; +import org.springframework.expression.spel.ast.CommonTypeDescriptors; import org.springframework.expression.spel.support.StandardEvaluationContext; /** @@ -67,8 +69,8 @@ public class MapAccessTests extends ExpressionTestCase { return (((Map) target).containsKey(name)); } - public Object read(EvaluationContext context, Object target, String name) throws AccessException { - return ((Map) target).get(name); + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + return new TypedValue(((Map) target).get(name),CommonTypeDescriptors.OBJECT_TYPE_DESCRIPTOR); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index 424768fff11..17796351eaa 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -16,6 +16,8 @@ package org.springframework.expression.spel; +import org.springframework.expression.spel.ast.Operator; + /** * Tests the evaluation of expressions using relational operators. * @@ -48,6 +50,12 @@ public class OperatorTests extends ExpressionTestCase { evaluate("6 == 6", true, Boolean.class); } + public void testNotEqual() { + evaluate("3 != 5", true, Boolean.class); + evaluate("5 != 3", true, Boolean.class); + evaluate("6 != 6", false, Boolean.class); + } + public void testGreaterThanOrEqual() { evaluate("3 >= 5", false, Boolean.class); evaluate("5 >= 3", true, Boolean.class); @@ -79,6 +87,10 @@ public class OperatorTests extends ExpressionTestCase { evaluate("3 % 2", 1, Integer.class); } + public void testDivide() { + evaluate("4L/2L",2L,Long.class); + } + public void testMathOperatorDivide_ConvertToDouble() { evaluateAndAskForReturnType("8/4", new Double(2.0), Double.class); } @@ -92,6 +104,10 @@ public class OperatorTests extends ExpressionTestCase { // } public void testDoubles() { + evaluate("3.0d == 5.0d", false, Boolean.class); + evaluate("3.0d == 3.0d", true, Boolean.class); + evaluate("3.0d != 5.0d", true, Boolean.class); + evaluate("3.0d != 3.0d", false, Boolean.class); evaluate("3.0d + 5.0d", 8.0d, Double.class); evaluate("3.0d - 5.0d", -2.0d, Double.class); evaluate("3.0d * 5.0d", 15.0d, Double.class); @@ -100,6 +116,10 @@ public class OperatorTests extends ExpressionTestCase { } public void testFloats() { + evaluate("3.0f == 5.0f", false, Boolean.class); + evaluate("3.0f == 3.0f", true, Boolean.class); + evaluate("3.0f != 5.0f", true, Boolean.class); + evaluate("3.0f != 3.0f", false, Boolean.class); evaluate("3.0f + 5.0f", 8.0d, Double.class); evaluate("3.0f - 5.0f", -2.0d, Double.class); evaluate("3.0f * 5.0f", 15.0d, Double.class); @@ -107,6 +127,17 @@ public class OperatorTests extends ExpressionTestCase { evaluate("5.0f % 3.1f", 1.9d, Double.class); } + public void testOperatorStrings() throws Exception { + Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3")); + assertEquals("==",node.getOperatorName()); + + node = getOperatorNode((SpelExpression)parser.parseExpression("1!=3")); + assertEquals("!=",node.getOperatorName()); + + node = getOperatorNode((SpelExpression)parser.parseExpression("3/3")); + assertEquals("/",node.getOperatorName()); + } + public void testMixedOperands_FloatsAndDoubles() { evaluate("3.0d + 5.0f", 8.0d, Double.class); evaluate("3.0D - 5.0f", -2.0d, Double.class); @@ -125,4 +156,37 @@ public class OperatorTests extends ExpressionTestCase { evaluate("5.5D % 3", 2.5, Double.class); } + public void testStrings() { + evaluate("'abc' == 'abc'",true,Boolean.class); + evaluate("'abc' == 'def'",false,Boolean.class); + evaluate("'abc' != 'abc'",false,Boolean.class); + evaluate("'abc' != 'def'",true,Boolean.class); + } + + public void testLongs() { + evaluate("3L == 4L", false, Boolean.class); + evaluate("3L == 3L", true, Boolean.class); + evaluate("3L != 4L", true, Boolean.class); + evaluate("3L != 3L", false, Boolean.class); + } + + private Operator getOperatorNode(SpelExpression e) { + SpelNode node = e.getAST(); + return (Operator)findNode(node,Operator.class); + } + + private SpelNode findNode(SpelNode node, Class clazz) { + if (clazz.isAssignableFrom(node.getClass())) { + return node; + } + int childCount = node.getChildCount(); + for (int i=0;i PlaceOfBirth + setValueExpectError("placesLivedList[0]", "Wien"); } + public void testSetGenericListElementValueTypeCoersionOK() { + setValue("booleanList[0]", "true", Boolean.TRUE); + } + public void testSetListElementNestedValue() { setValue("placesLived[0].city", "Wien"); } @@ -106,6 +113,46 @@ public class SetValueTests extends ExpressionTestCase { setValue("SomeProperty", "true", Boolean.TRUE); } + public void testAssign() throws Exception { + StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); + Expression e = parse("publicName='Andy'"); + assertFalse(e.isWritable(eContext)); + assertEquals("Andy",e.getValue(eContext)); + } + + /* + * Testing the coercion of both the keys and the values to the correct type + */ + public void testSetGenericMapElementRequiresCoercion() throws Exception { + StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); + Expression e = parse("mapOfStringToBoolean[42]"); + assertNull(e.getValue(eContext)); + + // Key should be coerced to string representation of 42 + e.setValue(eContext, "true"); + + // All keys should be strings + Set ks = parse("mapOfStringToBoolean.keySet()").getValue(eContext,Set.class); + for (Object o: ks) { + assertEquals(String.class,o.getClass()); + } + + // All values should be booleans + Collection vs = parse("mapOfStringToBoolean.values()").getValue(eContext,Collection.class); + for (Object o: vs) { + assertEquals(Boolean.class,o.getClass()); + } + + // One final test check coercion on the key for a map lookup + Object o = e.getValue(eContext); + assertEquals(Boolean.TRUE,o); + } + + + private Expression parse(String expressionString) throws Exception { + return parser.parseExpression(expressionString); + } + /** * Call setValue() but expect it to fail. */ @@ -167,7 +214,12 @@ public class SetValueTests extends ExpressionTestCase { StandardEvaluationContext lContext = TestScenarioCreator.getTestEvaluationContext(); assertTrue("Expression is not writeable but should be", e.isWritable(lContext)); e.setValue(lContext, value); - assertEquals("Retrieved value was not equal to set value", expectedValue, e.getValue(lContext)); + Object a = expectedValue; + Object b = e.getValue(lContext); + if (!a.equals(b)) { + fail("Not the same: ["+a+"] type="+a.getClass()+" ["+b+"] type="+b.getClass()); +// assertEquals("Retrieved value was not equal to set value", expectedValue, e.getValue(lContext)); + } } catch (EvaluationException ee) { ee.printStackTrace(); fail("Unexpected Exception: " + ee.getMessage()); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java index 4a72f2b6419..ef878107eba 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java @@ -9,6 +9,7 @@ import java.util.Map; @SuppressWarnings("unused") public class Inventor { private String name; + public String publicName; private PlaceOfBirth placeOfBirth; Date birthdate; private int sinNumber; @@ -23,6 +24,8 @@ public class Inventor { public boolean publicBoolean; private boolean accessedThroughGetSet; public List listOfInteger = new ArrayList(); + public List booleanList = new ArrayList(); + public Map mapOfStringToBoolean = new HashMap(); public Inventor(String name, Date birthdate, String nationality) { this.name = name; @@ -37,6 +40,8 @@ public class Inventor { testMap.put("friday", "freitag"); testMap.put("saturday", "samstag"); testMap.put("sunday", "sonntag"); + booleanList.add(false); + booleanList.add(false); } public void setPlaceOfBirth(PlaceOfBirth placeOfBirth2) { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java index 270ba8774b5..305a14b86b0 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java @@ -21,4 +21,16 @@ public class PlaceOfBirth { return i*2; } + public boolean equals(Object o) { + if (!(o instanceof PlaceOfBirth)) { + return false; + } + PlaceOfBirth oPOB = (PlaceOfBirth)o; + return (city.equals(oPOB.city)); + } + + public int hashCode() { + return city.hashCode(); + } + } \ No newline at end of file