From 4f68f8340060e2219a8e8f30d9401d3364049313 Mon Sep 17 00:00:00 2001 From: Ivo Smid Date: Sun, 18 Dec 2011 13:44:54 +0100 Subject: [PATCH] SPR-8308: OpPlus should convert operand values to String using registered converters implementation + unittest --- .../expression/spel/ast/OpPlus.java | 59 +++-- .../expression/spel/ast/OpPlusTest.java | 234 ++++++++++++++++++ 2 files changed, 278 insertions(+), 15 deletions(-) create mode 100644 org.springframework.expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTest.java diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java index eb1f55c82ae9..77ff1f8e8fc2 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java @@ -16,10 +16,13 @@ package org.springframework.expression.spel.ast; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; +import org.springframework.expression.TypeConverter; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; +import org.springframework.util.Assert; /** * The plus operator will: @@ -31,7 +34,7 @@ * * It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed * when the operand types vary (double+int=double). For other options it defers to the registered overloader. - * + * * @author Andy Clement * @since 3.0 */ @@ -39,6 +42,7 @@ public class OpPlus extends Operator { public OpPlus(int pos, SpelNodeImpl... operands) { super("+", pos, operands); + Assert.notEmpty(operands); } @Override @@ -48,19 +52,21 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep if (rightOp == null) { // If only one operand, then this is unary plus Object operandOne = leftOp.getValueInternal(state).getValue(); if (operandOne instanceof Number) { - if (operandOne instanceof Double) { - return new TypedValue(((Double) operandOne).doubleValue()); - } else if (operandOne instanceof Long) { - return new TypedValue(((Long) operandOne).longValue()); + if (operandOne instanceof Double || operandOne instanceof Long) { + return new TypedValue(operandOne); } else { - return new TypedValue(((Integer) operandOne).intValue()); + return new TypedValue(((Number) operandOne).intValue()); } } return state.operate(Operation.ADD, operandOne, null); } else { - Object operandOne = leftOp.getValueInternal(state).getValue(); - Object operandTwo = rightOp.getValueInternal(state).getValue(); + final TypedValue operandOneValue = leftOp.getValueInternal(state); + final Object operandOne = operandOneValue.getValue(); + + final TypedValue operandTwoValue = rightOp.getValueInternal(state); + final Object operandTwo = operandTwoValue.getValue(); + if (operandOne instanceof Number && operandTwo instanceof Number) { Number op1 = (Number) operandOne; Number op2 = (Number) operandTwo; @@ -74,13 +80,14 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep } else if (operandOne instanceof String && operandTwo instanceof String) { return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString()); } else if (operandOne instanceof String) { - StringBuilder result = new StringBuilder((String)operandOne); - result.append((operandTwo==null?"null":operandTwo.toString())); - return new TypedValue(result.toString()); + StringBuilder result = new StringBuilder((String) operandOne); + result.append((operandTwo == null ? "null" : convertTypedValueToString(operandTwoValue, state))); + return new TypedValue(result.toString()); } else if (operandTwo instanceof String) { - StringBuilder result = new StringBuilder((operandOne==null?"null":operandOne.toString())); - result.append((String)operandTwo); - return new TypedValue(result.toString()); + StringBuilder result = new StringBuilder((operandOne == null ? "null" : convertTypedValueToString( + operandOneValue, state))); + result.append((String) operandTwo); + return new TypedValue(result.toString()); } return state.operate(Operation.ADD, operandOne, operandTwo); } @@ -94,10 +101,32 @@ public String toStringAST() { return super.toStringAST(); } + @Override public SpelNodeImpl getRightOperand() { - if (children.length<2) {return null;} + if (children.length < 2) { + return null; + } return children[1]; } + /** + * Convert operand value to string using registered converter or using toString method. + * + * @param value typed value to be converted + * @param state expression state + * @return TypedValue instance converted to String + */ + protected static String convertTypedValueToString(TypedValue value, ExpressionState state) { + final TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter(); + final TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class); + + if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) { + final Object obj = typeConverter.convertValue(value.getValue(), value.getTypeDescriptor(), typeDescriptor); + return String.valueOf(obj); + } else { + return String.valueOf(value.getValue()); + } + + } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTest.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTest.java new file mode 100644 index 000000000000..232be2c72f03 --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTest.java @@ -0,0 +1,234 @@ +/* + * Copyright 2002-2011 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 java.sql.Time; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import org.junit.Test; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeConverter; + +import static org.junit.Assert.*; + +/** + * The plus operator test + * + * @author Ivo Smid + * @since 3.0 + */ +public class OpPlusTest { + + @Test(expected = IllegalArgumentException.class) + public void test_emptyOperands() { + new OpPlus(-1); + } + + @Test(expected = SpelEvaluationException.class) + public void test_unaryPlusWithStringLiteral() { + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + StringLiteral str = new StringLiteral("word", -1, "word"); + + OpPlus o = new OpPlus(-1, str); + o.getValueInternal(expressionState); + } + + @Test + public void test_unaryPlusWithNumberOperand() { + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + { + RealLiteral realLiteral = new RealLiteral("123.00", -1, 123.0); + OpPlus o = new OpPlus(-1, realLiteral); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(Double.class, value.getTypeDescriptor().getObjectType()); + assertEquals(Double.class, value.getTypeDescriptor().getType()); + assertEquals(realLiteral.getLiteralValue().getValue(), value.getValue()); + } + + { + IntLiteral intLiteral = new IntLiteral("123", -1, 123); + OpPlus o = new OpPlus(-1, intLiteral); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(Integer.class, value.getTypeDescriptor().getObjectType()); + assertEquals(Integer.class, value.getTypeDescriptor().getType()); + assertEquals(intLiteral.getLiteralValue().getValue(), value.getValue()); + } + + { + LongLiteral longLiteral = new LongLiteral("123", -1, 123L); + OpPlus o = new OpPlus(-1, longLiteral); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(Long.class, value.getTypeDescriptor().getObjectType()); + assertEquals(Long.class, value.getTypeDescriptor().getType()); + assertEquals(longLiteral.getLiteralValue().getValue(), value.getValue()); + } + + + } + + @Test + public void test_binaryPlusWithNumberOperands() { + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + { + RealLiteral n1 = new RealLiteral("123.00", -1, 123.0); + RealLiteral n2 = new RealLiteral("456.00", -1, 456.0); + OpPlus o = new OpPlus(-1, n1, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(Double.class, value.getTypeDescriptor().getObjectType()); + assertEquals(Double.class, value.getTypeDescriptor().getType()); + assertEquals(Double.valueOf(123.0 + 456.0), value.getValue()); + } + + { + LongLiteral n1 = new LongLiteral("123", -1, 123L); + LongLiteral n2 = new LongLiteral("456", -1, 456L); + OpPlus o = new OpPlus(-1, n1, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(Long.class, value.getTypeDescriptor().getObjectType()); + assertEquals(Long.class, value.getTypeDescriptor().getType()); + assertEquals(Long.valueOf(123L + 456L), value.getValue()); + } + + { + IntLiteral n1 = new IntLiteral("123", -1, 123); + IntLiteral n2 = new IntLiteral("456", -1, 456); + OpPlus o = new OpPlus(-1, n1, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(Integer.class, value.getTypeDescriptor().getObjectType()); + assertEquals(Integer.class, value.getTypeDescriptor().getType()); + assertEquals(Integer.valueOf(123 + 456), value.getValue()); + } + + } + + @Test + public void test_binaryPlusWithStringOperands() { + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + StringLiteral n1 = new StringLiteral("\"foo\"", -1, "\"foo\""); + StringLiteral n2 = new StringLiteral("\"bar\"", -1, "\"bar\""); + OpPlus o = new OpPlus(-1, n1, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(String.class, value.getTypeDescriptor().getObjectType()); + assertEquals(String.class, value.getTypeDescriptor().getType()); + assertEquals("foobar", value.getValue()); + + } + + @Test + public void test_binaryPlusWithLeftStringOperand() { + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + StringLiteral n1 = new StringLiteral("\"number is \"", -1, "\"number is \""); + LongLiteral n2 = new LongLiteral("123", -1, 123); + OpPlus o = new OpPlus(-1, n1, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(String.class, value.getTypeDescriptor().getObjectType()); + assertEquals(String.class, value.getTypeDescriptor().getType()); + assertEquals("number is 123", value.getValue()); + + } + + @Test + public void test_binaryPlusWithRightStringOperand() { + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + LongLiteral n1 = new LongLiteral("123", -1, 123); + StringLiteral n2 = new StringLiteral("\" is a number\"", -1, "\" is a number\""); + OpPlus o = new OpPlus(-1, n1, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(String.class, value.getTypeDescriptor().getObjectType()); + assertEquals(String.class, value.getTypeDescriptor().getType()); + assertEquals("123 is a number", value.getValue()); + + } + + @Test + public void test_binaryPlusWithTime_ToString() { + + ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext()); + + Time time = new Time(new Date().getTime()); + + VariableReference var = new VariableReference("timeVar", -1); + var.setValue(expressionState, time); + + StringLiteral n2 = new StringLiteral("\" is now\"", -1, "\" is now\""); + OpPlus o = new OpPlus(-1, var, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(String.class, value.getTypeDescriptor().getObjectType()); + assertEquals(String.class, value.getTypeDescriptor().getType()); + assertEquals(time + " is now", value.getValue()); + + } + + @Test + public void test_binaryPlusWithTimeConverted() { + + final SimpleDateFormat format = new SimpleDateFormat("hh :--: mm :--: ss", Locale.ENGLISH); + + GenericConversionService conversionService = new GenericConversionService(); + conversionService.addConverter(new Converter() { + public String convert(Time source) { + return format.format(source); + } + }); + + StandardEvaluationContext evaluationContextConverter = new StandardEvaluationContext(); + evaluationContextConverter.setTypeConverter(new StandardTypeConverter(conversionService)); + + ExpressionState expressionState = new ExpressionState(evaluationContextConverter); + + Time time = new Time(new Date().getTime()); + + VariableReference var = new VariableReference("timeVar", -1); + var.setValue(expressionState, time); + + + StringLiteral n2 = new StringLiteral("\" is now\"", -1, "\" is now\""); + OpPlus o = new OpPlus(-1, var, n2); + TypedValue value = o.getValueInternal(expressionState); + + assertEquals(String.class, value.getTypeDescriptor().getObjectType()); + assertEquals(String.class, value.getTypeDescriptor().getType()); + assertEquals(format.format(time) + " is now", value.getValue()); + + } + + +}