Skip to content

SPR-8308: OpPlus should convert operand values to String using registered converters #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -31,14 +34,15 @@
* </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
* @since 3.0
*/
public class OpPlus extends Operator {

public OpPlus(int pos, SpelNodeImpl... operands) {
super("+", pos, operands);
Assert.notEmpty(operands);
}

@Override
Expand All @@ -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;
Expand All @@ -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);
}
Expand All @@ -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 <code>toString</code> method.
*
* @param value typed value to be converted
* @param state expression state
* @return <code>TypedValue</code> instance converted to <code>String</code>
*/
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());
}

}

}
Original file line number Diff line number Diff line change
@@ -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<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());

}


}