Skip to content

Commit e85e614

Browse files
committed
Merge pull request #7 from bedla/SPR-8308
* SPR-8308: Convert SpEL plus operands using reg'd converters
2 parents 3514cc5 + 7cdfaf3 commit e85e614

File tree

2 files changed

+272
-16
lines changed
  • spring-expression/src

2 files changed

+272
-16
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,13 @@
1616

1717
package org.springframework.expression.spel.ast;
1818

19+
import org.springframework.core.convert.TypeDescriptor;
1920
import org.springframework.expression.EvaluationException;
2021
import org.springframework.expression.Operation;
22+
import org.springframework.expression.TypeConverter;
2123
import org.springframework.expression.TypedValue;
2224
import org.springframework.expression.spel.ExpressionState;
25+
import org.springframework.util.Assert;
2326

2427
/**
2528
* The plus operator will:
@@ -31,14 +34,16 @@
3134
* </ul>
3235
* It can be used as a unary operator for numbers (double/long/int). The standard promotions are performed
3336
* when the operand types vary (double+int=double). For other options it defers to the registered overloader.
34-
*
37+
*
3538
* @author Andy Clement
39+
* @author Ivo Smid
3640
* @since 3.0
3741
*/
3842
public class OpPlus extends Operator {
3943

4044
public OpPlus(int pos, SpelNodeImpl... operands) {
4145
super("+", pos, operands);
46+
Assert.notEmpty(operands);
4247
}
4348

4449
@Override
@@ -48,19 +53,21 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
4853
if (rightOp == null) { // If only one operand, then this is unary plus
4954
Object operandOne = leftOp.getValueInternal(state).getValue();
5055
if (operandOne instanceof Number) {
51-
if (operandOne instanceof Double) {
52-
return new TypedValue(((Double) operandOne).doubleValue());
53-
} else if (operandOne instanceof Long) {
54-
return new TypedValue(((Long) operandOne).longValue());
56+
if (operandOne instanceof Double || operandOne instanceof Long) {
57+
return new TypedValue(operandOne);
5558
} else {
56-
return new TypedValue(((Integer) operandOne).intValue());
59+
return new TypedValue(((Number) operandOne).intValue());
5760
}
5861
}
5962
return state.operate(Operation.ADD, operandOne, null);
6063
}
6164
else {
62-
Object operandOne = leftOp.getValueInternal(state).getValue();
63-
Object operandTwo = rightOp.getValueInternal(state).getValue();
65+
final TypedValue operandOneValue = leftOp.getValueInternal(state);
66+
final Object operandOne = operandOneValue.getValue();
67+
68+
final TypedValue operandTwoValue = rightOp.getValueInternal(state);
69+
final Object operandTwo = operandTwoValue.getValue();
70+
6471
if (operandOne instanceof Number && operandTwo instanceof Number) {
6572
Number op1 = (Number) operandOne;
6673
Number op2 = (Number) operandTwo;
@@ -74,13 +81,14 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
7481
} else if (operandOne instanceof String && operandTwo instanceof String) {
7582
return new TypedValue(new StringBuilder((String) operandOne).append((String) operandTwo).toString());
7683
} else if (operandOne instanceof String) {
77-
StringBuilder result = new StringBuilder((String)operandOne);
78-
result.append((operandTwo==null?"null":operandTwo.toString()));
79-
return new TypedValue(result.toString());
84+
StringBuilder result = new StringBuilder((String) operandOne);
85+
result.append((operandTwo == null ? "null" : convertTypedValueToString(operandTwoValue, state)));
86+
return new TypedValue(result.toString());
8087
} else if (operandTwo instanceof String) {
81-
StringBuilder result = new StringBuilder((operandOne==null?"null":operandOne.toString()));
82-
result.append((String)operandTwo);
83-
return new TypedValue(result.toString());
88+
StringBuilder result = new StringBuilder((operandOne == null ? "null" : convertTypedValueToString(
89+
operandOneValue, state)));
90+
result.append((String) operandTwo);
91+
return new TypedValue(result.toString());
8492
}
8593
return state.operate(Operation.ADD, operandOne, operandTwo);
8694
}
@@ -94,10 +102,32 @@ public String toStringAST() {
94102
return super.toStringAST();
95103
}
96104

105+
@Override
97106
public SpelNodeImpl getRightOperand() {
98-
if (children.length<2) {return null;}
107+
if (children.length < 2) {
108+
return null;
109+
}
99110
return children[1];
100111
}
101112

113+
/**
114+
* Convert operand value to string using registered converter or using
115+
* {@code toString} method.
116+
*
117+
* @param value typed value to be converted
118+
* @param state expression state
119+
* @return {@code TypedValue} instance converted to {@code String}
120+
*/
121+
private static String convertTypedValueToString(TypedValue value, ExpressionState state) {
122+
final TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
123+
final TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class);
124+
125+
if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) {
126+
final Object obj = typeConverter.convertValue(value.getValue(), value.getTypeDescriptor(), typeDescriptor);
127+
return String.valueOf(obj);
128+
} else {
129+
return String.valueOf(value.getValue());
130+
}
131+
}
102132

103133
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.expression.spel.ast;
18+
19+
import java.sql.Time;
20+
import java.text.SimpleDateFormat;
21+
import java.util.Date;
22+
import java.util.Locale;
23+
24+
import org.junit.Test;
25+
26+
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.core.convert.support.GenericConversionService;
28+
import org.springframework.expression.TypedValue;
29+
import org.springframework.expression.spel.ExpressionState;
30+
import org.springframework.expression.spel.SpelEvaluationException;
31+
import org.springframework.expression.spel.support.StandardEvaluationContext;
32+
import org.springframework.expression.spel.support.StandardTypeConverter;
33+
34+
import static org.junit.Assert.*;
35+
36+
/**
37+
* Unit tests for SpEL's plus operator.
38+
*
39+
* @author Ivo Smid
40+
* @author Chris Beams
41+
* @since 3.2
42+
* @see OpPlus
43+
*/
44+
public class OpPlusTests {
45+
46+
@Test(expected = IllegalArgumentException.class)
47+
public void test_emptyOperands() {
48+
new OpPlus(-1);
49+
}
50+
51+
@Test(expected = SpelEvaluationException.class)
52+
public void test_unaryPlusWithStringLiteral() {
53+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
54+
55+
StringLiteral str = new StringLiteral("word", -1, "word");
56+
57+
OpPlus o = new OpPlus(-1, str);
58+
o.getValueInternal(expressionState);
59+
}
60+
61+
@Test
62+
public void test_unaryPlusWithNumberOperand() {
63+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
64+
65+
{
66+
RealLiteral realLiteral = new RealLiteral("123.00", -1, 123.0);
67+
OpPlus o = new OpPlus(-1, realLiteral);
68+
TypedValue value = o.getValueInternal(expressionState);
69+
70+
assertEquals(Double.class, value.getTypeDescriptor().getObjectType());
71+
assertEquals(Double.class, value.getTypeDescriptor().getType());
72+
assertEquals(realLiteral.getLiteralValue().getValue(), value.getValue());
73+
}
74+
75+
{
76+
IntLiteral intLiteral = new IntLiteral("123", -1, 123);
77+
OpPlus o = new OpPlus(-1, intLiteral);
78+
TypedValue value = o.getValueInternal(expressionState);
79+
80+
assertEquals(Integer.class, value.getTypeDescriptor().getObjectType());
81+
assertEquals(Integer.class, value.getTypeDescriptor().getType());
82+
assertEquals(intLiteral.getLiteralValue().getValue(), value.getValue());
83+
}
84+
85+
{
86+
LongLiteral longLiteral = new LongLiteral("123", -1, 123L);
87+
OpPlus o = new OpPlus(-1, longLiteral);
88+
TypedValue value = o.getValueInternal(expressionState);
89+
90+
assertEquals(Long.class, value.getTypeDescriptor().getObjectType());
91+
assertEquals(Long.class, value.getTypeDescriptor().getType());
92+
assertEquals(longLiteral.getLiteralValue().getValue(), value.getValue());
93+
}
94+
}
95+
96+
@Test
97+
public void test_binaryPlusWithNumberOperands() {
98+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
99+
100+
{
101+
RealLiteral n1 = new RealLiteral("123.00", -1, 123.0);
102+
RealLiteral n2 = new RealLiteral("456.00", -1, 456.0);
103+
OpPlus o = new OpPlus(-1, n1, n2);
104+
TypedValue value = o.getValueInternal(expressionState);
105+
106+
assertEquals(Double.class, value.getTypeDescriptor().getObjectType());
107+
assertEquals(Double.class, value.getTypeDescriptor().getType());
108+
assertEquals(Double.valueOf(123.0 + 456.0), value.getValue());
109+
}
110+
111+
{
112+
LongLiteral n1 = new LongLiteral("123", -1, 123L);
113+
LongLiteral n2 = new LongLiteral("456", -1, 456L);
114+
OpPlus o = new OpPlus(-1, n1, n2);
115+
TypedValue value = o.getValueInternal(expressionState);
116+
117+
assertEquals(Long.class, value.getTypeDescriptor().getObjectType());
118+
assertEquals(Long.class, value.getTypeDescriptor().getType());
119+
assertEquals(Long.valueOf(123L + 456L), value.getValue());
120+
}
121+
122+
{
123+
IntLiteral n1 = new IntLiteral("123", -1, 123);
124+
IntLiteral n2 = new IntLiteral("456", -1, 456);
125+
OpPlus o = new OpPlus(-1, n1, n2);
126+
TypedValue value = o.getValueInternal(expressionState);
127+
128+
assertEquals(Integer.class, value.getTypeDescriptor().getObjectType());
129+
assertEquals(Integer.class, value.getTypeDescriptor().getType());
130+
assertEquals(Integer.valueOf(123 + 456), value.getValue());
131+
}
132+
}
133+
134+
@Test
135+
public void test_binaryPlusWithStringOperands() {
136+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
137+
138+
StringLiteral n1 = new StringLiteral("\"foo\"", -1, "\"foo\"");
139+
StringLiteral n2 = new StringLiteral("\"bar\"", -1, "\"bar\"");
140+
OpPlus o = new OpPlus(-1, n1, n2);
141+
TypedValue value = o.getValueInternal(expressionState);
142+
143+
assertEquals(String.class, value.getTypeDescriptor().getObjectType());
144+
assertEquals(String.class, value.getTypeDescriptor().getType());
145+
assertEquals("foobar", value.getValue());
146+
}
147+
148+
@Test
149+
public void test_binaryPlusWithLeftStringOperand() {
150+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
151+
152+
StringLiteral n1 = new StringLiteral("\"number is \"", -1, "\"number is \"");
153+
LongLiteral n2 = new LongLiteral("123", -1, 123);
154+
OpPlus o = new OpPlus(-1, n1, n2);
155+
TypedValue value = o.getValueInternal(expressionState);
156+
157+
assertEquals(String.class, value.getTypeDescriptor().getObjectType());
158+
assertEquals(String.class, value.getTypeDescriptor().getType());
159+
assertEquals("number is 123", value.getValue());
160+
}
161+
162+
@Test
163+
public void test_binaryPlusWithRightStringOperand() {
164+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
165+
166+
LongLiteral n1 = new LongLiteral("123", -1, 123);
167+
StringLiteral n2 = new StringLiteral("\" is a number\"", -1, "\" is a number\"");
168+
OpPlus o = new OpPlus(-1, n1, n2);
169+
TypedValue value = o.getValueInternal(expressionState);
170+
171+
assertEquals(String.class, value.getTypeDescriptor().getObjectType());
172+
assertEquals(String.class, value.getTypeDescriptor().getType());
173+
assertEquals("123 is a number", value.getValue());
174+
}
175+
176+
@Test
177+
public void test_binaryPlusWithTime_ToString() {
178+
179+
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
180+
181+
Time time = new Time(new Date().getTime());
182+
183+
VariableReference var = new VariableReference("timeVar", -1);
184+
var.setValue(expressionState, time);
185+
186+
StringLiteral n2 = new StringLiteral("\" is now\"", -1, "\" is now\"");
187+
OpPlus o = new OpPlus(-1, var, n2);
188+
TypedValue value = o.getValueInternal(expressionState);
189+
190+
assertEquals(String.class, value.getTypeDescriptor().getObjectType());
191+
assertEquals(String.class, value.getTypeDescriptor().getType());
192+
assertEquals(time + " is now", value.getValue());
193+
}
194+
195+
@Test
196+
public void test_binaryPlusWithTimeConverted() {
197+
198+
final SimpleDateFormat format = new SimpleDateFormat("hh :--: mm :--: ss", Locale.ENGLISH);
199+
200+
GenericConversionService conversionService = new GenericConversionService();
201+
conversionService.addConverter(new Converter<Time, String>() {
202+
public String convert(Time source) {
203+
return format.format(source);
204+
}
205+
});
206+
207+
StandardEvaluationContext evaluationContextConverter = new StandardEvaluationContext();
208+
evaluationContextConverter.setTypeConverter(new StandardTypeConverter(conversionService));
209+
210+
ExpressionState expressionState = new ExpressionState(evaluationContextConverter);
211+
212+
Time time = new Time(new Date().getTime());
213+
214+
VariableReference var = new VariableReference("timeVar", -1);
215+
var.setValue(expressionState, time);
216+
217+
StringLiteral n2 = new StringLiteral("\" is now\"", -1, "\" is now\"");
218+
OpPlus o = new OpPlus(-1, var, n2);
219+
TypedValue value = o.getValueInternal(expressionState);
220+
221+
assertEquals(String.class, value.getTypeDescriptor().getObjectType());
222+
assertEquals(String.class, value.getTypeDescriptor().getType());
223+
assertEquals(format.format(time) + " is now", value.getValue());
224+
}
225+
226+
}

0 commit comments

Comments
 (0)