View Javadoc
1   /*
2    * Copyright 2002-2014 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.math.BigDecimal;
20  import java.math.BigInteger;
21  
22  import org.springframework.asm.MethodVisitor;
23  import org.springframework.core.convert.TypeDescriptor;
24  import org.springframework.expression.EvaluationException;
25  import org.springframework.expression.Operation;
26  import org.springframework.expression.TypeConverter;
27  import org.springframework.expression.TypedValue;
28  import org.springframework.expression.spel.CodeFlow;
29  import org.springframework.expression.spel.ExpressionState;
30  import org.springframework.util.Assert;
31  import org.springframework.util.NumberUtils;
32  
33  /**
34   * The plus operator will:
35   * <ul>
36   * <li>add numbers
37   * <li>concatenate strings
38   * </ul>
39   *
40   * <p>It can be used as a unary operator for numbers.
41   * The standard promotions are performed when the operand types vary (double+int=double).
42   * For other options it defers to the registered overloader.
43   *
44   * @author Andy Clement
45   * @author Juergen Hoeller
46   * @author Ivo Smid
47   * @author Giovanni Dall'Oglio Risso
48   * @since 3.0
49   */
50  public class OpPlus extends Operator {
51  
52  	public OpPlus(int pos, SpelNodeImpl... operands) {
53  		super("+", pos, operands);
54  		Assert.notEmpty(operands);
55  	}
56  
57  
58  	@Override
59  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
60  		SpelNodeImpl leftOp = getLeftOperand();
61  		SpelNodeImpl rightOp = getRightOperand();
62  
63  		if (rightOp == null) {  // if only one operand, then this is unary plus
64  			Object operandOne = leftOp.getValueInternal(state).getValue();
65  			if (operandOne instanceof Number) {
66  				if (operandOne instanceof Double) {
67  					this.exitTypeDescriptor = "D";
68  				}
69  				else if (operandOne instanceof Float) {
70  					this.exitTypeDescriptor = "F";
71  				}
72  				else if (operandOne instanceof Long) {
73  					this.exitTypeDescriptor = "J";
74  				}
75  				else if (operandOne instanceof Integer) {
76  					this.exitTypeDescriptor = "I";
77  				}
78  				return new TypedValue(operandOne);
79  			}
80  			return state.operate(Operation.ADD, operandOne, null);
81  		}
82  
83  		TypedValue operandOneValue = leftOp.getValueInternal(state);
84  		Object leftOperand = operandOneValue.getValue();
85  		TypedValue operandTwoValue = rightOp.getValueInternal(state);
86  		Object rightOperand = operandTwoValue.getValue();
87  
88  		if (leftOperand instanceof Number && rightOperand instanceof Number) {
89  			Number leftNumber = (Number) leftOperand;
90  			Number rightNumber = (Number) rightOperand;
91  
92  			if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
93  				BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
94  				BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
95  				return new TypedValue(leftBigDecimal.add(rightBigDecimal));
96  			}
97  			else if (leftNumber instanceof Double || rightNumber instanceof Double) {
98  				if (leftNumber.getClass() == rightNumber.getClass()) {
99  					this.exitTypeDescriptor = "D";
100 				}
101 				return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
102 			}
103 			else if (leftNumber instanceof Float || rightNumber instanceof Float) {
104 				if (leftNumber.getClass() == rightNumber.getClass()) {
105 					this.exitTypeDescriptor = "F";
106 				}
107 				return new TypedValue(leftNumber.floatValue() + rightNumber.floatValue());
108 			}
109 			else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
110 				BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
111 				BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
112 				return new TypedValue(leftBigInteger.add(rightBigInteger));
113 			}
114 			else if (leftNumber instanceof Long || rightNumber instanceof Long) {
115 				if (leftNumber.getClass() == rightNumber.getClass()) {
116 					this.exitTypeDescriptor = "J";
117 				}
118 				return new TypedValue(leftNumber.longValue() + rightNumber.longValue());
119 			}
120 			else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNumericOp(rightNumber)) {
121 				if (leftNumber instanceof Integer && rightNumber instanceof Integer) {
122 					this.exitTypeDescriptor = "I";
123 				}
124 				return new TypedValue(leftNumber.intValue() + rightNumber.intValue());
125 			}
126 			else {
127 				// Unknown Number subtypes -> best guess is double addition
128 				return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
129 			}
130 		}
131 
132 		if (leftOperand instanceof String && rightOperand instanceof String) {
133 			this.exitTypeDescriptor = "Ljava/lang/String";
134 			return new TypedValue((String) leftOperand + rightOperand);
135 		}
136 
137 		if (leftOperand instanceof String) {
138 			return new TypedValue(
139 					leftOperand + (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state)));
140 		}
141 
142 		if (rightOperand instanceof String) {
143 			return new TypedValue(
144 					(leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state)) + rightOperand);
145 		}
146 
147 		return state.operate(Operation.ADD, leftOperand, rightOperand);
148 	}
149 
150 	@Override
151 	public String toStringAST() {
152 		if (this.children.length < 2) {  // unary plus
153 			return "+" + getLeftOperand().toStringAST();
154 		}
155 		return super.toStringAST();
156 	}
157 
158 	@Override
159 	public SpelNodeImpl getRightOperand() {
160 		if (this.children.length < 2) {
161 			return null;
162 		}
163 		return this.children[1];
164 	}
165 
166 	/**
167 	 * Convert operand value to string using registered converter or using
168 	 * {@code toString} method.
169 	 * @param value typed value to be converted
170 	 * @param state expression state
171 	 * @return {@code TypedValue} instance converted to {@code String}
172 	 */
173 	private static String convertTypedValueToString(TypedValue value, ExpressionState state) {
174 		TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
175 		TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class);
176 		if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) {
177 			return String.valueOf(typeConverter.convertValue(value.getValue(),
178 					value.getTypeDescriptor(), typeDescriptor));
179 		}
180 		return String.valueOf(value.getValue());
181 	}
182 
183 	@Override
184 	public boolean isCompilable() {
185 		if (!getLeftOperand().isCompilable()) {
186 			return false;
187 		}
188 		if (this.children.length > 1) {
189 			 if (!getRightOperand().isCompilable()) {
190 				 return false;
191 			 }
192 		}
193 		return (this.exitTypeDescriptor != null);
194 	}
195 
196 	/**
197 	 * Walk through a possible tree of nodes that combine strings and append
198 	 * them all to the same (on stack) StringBuilder.
199 	 */
200 	private void walk(MethodVisitor mv, CodeFlow cf, SpelNodeImpl operand) {
201 		if (operand instanceof OpPlus) {
202 			OpPlus plus = (OpPlus)operand;
203 			walk(mv,cf,plus.getLeftOperand());
204 			walk(mv,cf,plus.getRightOperand());
205 		}
206 		else {
207 			cf.enterCompilationScope();
208 			operand.generateCode(mv,cf);
209 			if (cf.lastDescriptor() != "Ljava/lang/String") {
210 				mv.visitTypeInsn(CHECKCAST, "java/lang/String");
211 			}
212 			cf.exitCompilationScope();
213 			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
214 		}
215 	}
216 	
217 	@Override
218 	public void generateCode(MethodVisitor mv, CodeFlow cf) {
219 		if (this.exitTypeDescriptor == "Ljava/lang/String") {
220 			mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
221 			mv.visitInsn(DUP);
222 			mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
223 			walk(mv,cf,getLeftOperand());
224 			walk(mv,cf,getRightOperand());
225 			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
226 		}
227 		else {
228 			getLeftOperand().generateCode(mv, cf);
229 			String leftDesc = getLeftOperand().exitTypeDescriptor;
230 			if (!CodeFlow.isPrimitive(leftDesc)) {
231 				CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), leftDesc);
232 			}
233 			if (this.children.length > 1) {
234 				cf.enterCompilationScope();
235 				getRightOperand().generateCode(mv, cf);
236 				String rightDesc = getRightOperand().exitTypeDescriptor;
237 				cf.exitCompilationScope();
238 				if (!CodeFlow.isPrimitive(rightDesc)) {
239 					CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), rightDesc);
240 				}
241 				switch (this.exitTypeDescriptor.charAt(0)) {
242 					case 'I':
243 						mv.visitInsn(IADD);
244 						break;
245 					case 'J':
246 						mv.visitInsn(LADD);
247 						break;
248 					case 'F': 
249 						mv.visitInsn(FADD);
250 						break;
251 					case 'D':
252 						mv.visitInsn(DADD);
253 						break;				
254 					default:
255 						throw new IllegalStateException(
256 								"Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
257 				}
258 			}
259 		}
260 		cf.pushDescriptor(this.exitTypeDescriptor);
261 	}
262 
263 }