Convert SpEL plus operands using reg'd converters

Prior to this commit, SpEL's OpPlus ('+' operator) would convert its
left and right operands to String directly via #toString calls.

This change ensures that operand values are delegated to any registered
converters, allowing for customization of the stringified output.

Note that the OpPlus constructor now throws IllegalArgumentException if
zero operands are supplied.

Issue: SPR-8308
This commit is contained in:
Ivo Smid 2011-12-18 13:44:54 +01:00 committed by Chris Beams
parent 3514cc5d65
commit 7cdfaf3e0d
2 changed files with 272 additions and 16 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,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,14 +34,16 @@ import org.springframework.expression.spel.ExpressionState;
* </ul>
* 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
* @author Ivo Smid
* @since 3.0
*/
public class OpPlus extends Operator {
public OpPlus(int pos, SpelNodeImpl... operands) {
super("+", pos, operands);
Assert.notEmpty(operands);
}
@Override
@ -48,19 +53,21 @@ public class OpPlus extends Operator {
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 +81,14 @@ public class OpPlus extends Operator {
} 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 +102,32 @@ public class OpPlus extends Operator {
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
* {@code toString} method.
*
* @param value typed value to be converted
* @param state expression state
* @return {@code TypedValue} instance converted to {@code String}
*/
private 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());
}
}
}

View File

@ -0,0 +1,226 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.ast;
import 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.*;
/**
* Unit tests for SpEL's plus operator.
*
* @author Ivo Smid
* @author Chris Beams
* @since 3.2
* @see OpPlus
*/
public class OpPlusTests {
@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<Time, String>() {
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());
}
}