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.expression.EvaluationException;
24  import org.springframework.expression.Operation;
25  import org.springframework.expression.TypedValue;
26  import org.springframework.expression.spel.CodeFlow;
27  import org.springframework.expression.spel.ExpressionState;
28  import org.springframework.util.NumberUtils;
29  
30  /**
31   * Implements the {@code multiply} operator.
32   *
33   * <p>Conversions and promotions are handled as defined in
34   * <a href="http://java.sun.com/docs/books/jls/third_edition/html/conversions.html">Section 5.6.2 of the
35   * Java Language Specification</a>, with the addiction of {@code BigDecimal}/{@code BigInteger} management:
36   *
37   * <p>If any of the operands is of a reference type, unboxing conversion (Section 5.1.8)
38   * is performed. Then:<br>
39   * If either operand is of type {@code BigDecimal}, the other is converted to {@code BigDecimal}.<br>
40   * If either operand is of type double, the other is converted to double.<br>
41   * Otherwise, if either operand is of type float, the other is converted to float.<br>
42   * If either operand is of type {@code BigInteger}, the other is converted to {@code BigInteger}.<br>
43   * Otherwise, if either operand is of type long, the other is converted to long.<br>
44   * Otherwise, both operands are converted to type int.
45   *
46   * @author Andy Clement
47   * @author Juergen Hoeller
48   * @author Sam Brannen
49   * @author Giovanni Dall'Oglio Risso
50   * @since 3.0
51   */
52  public class OpMultiply extends Operator {
53  
54  	public OpMultiply(int pos, SpelNodeImpl... operands) {
55  		super("*", pos, operands);
56  	}
57  
58  
59  	/**
60  	 * Implements the {@code multiply} operator directly here for certain types
61  	 * of supported operands and otherwise delegates to any registered overloader
62  	 * for types not supported here.
63  	 * <p>Supported operand types:
64  	 * <ul>
65  	 * <li>numbers
66  	 * <li>String and int ('abc' * 2 == 'abcabc')
67  	 * </ul>
68  	 */
69  	@Override
70  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
71  		Object leftOperand = getLeftOperand().getValueInternal(state).getValue();
72  		Object rightOperand = getRightOperand().getValueInternal(state).getValue();
73  
74  		if (leftOperand instanceof Number && rightOperand instanceof Number) {
75  			Number leftNumber = (Number) leftOperand;
76  			Number rightNumber = (Number) rightOperand;
77  
78  			if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
79  				BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
80  				BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
81  				return new TypedValue(leftBigDecimal.multiply(rightBigDecimal));
82  			}
83  			else if (leftNumber instanceof Double || rightNumber instanceof Double) {
84  				if (leftNumber.getClass() == rightNumber.getClass()) {
85  					this.exitTypeDescriptor = "D";
86  				}
87  				return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
88  			}
89  			else if (leftNumber instanceof Float || rightNumber instanceof Float) {
90  				if (leftNumber.getClass() == rightNumber.getClass()) {
91  					this.exitTypeDescriptor = "F";
92  				}
93  				return new TypedValue(leftNumber.floatValue() * rightNumber.floatValue());
94  			}
95  			else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
96  				BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
97  				BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
98  				return new TypedValue(leftBigInteger.multiply(rightBigInteger));
99  			}
100 			else if (leftNumber instanceof Long || rightNumber instanceof Long) {
101 				if (leftNumber.getClass() == rightNumber.getClass()) {
102 					this.exitTypeDescriptor = "J";
103 				}
104 				return new TypedValue(leftNumber.longValue() * rightNumber.longValue());
105 			}
106 			else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNumericOp(rightNumber)) {
107 				if (leftNumber instanceof Integer && rightNumber instanceof Integer) {
108 					this.exitTypeDescriptor = "I";
109 				}
110 				return new TypedValue(leftNumber.intValue() * rightNumber.intValue());
111 			}
112 			else {
113 				// Unknown Number subtypes -> best guess is double multiplication
114 				return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
115 			}
116 		}
117 
118 		if (leftOperand instanceof String && rightOperand instanceof Integer) {
119 			int repeats = (Integer) rightOperand;
120 			StringBuilder result = new StringBuilder();
121 			for (int i = 0; i < repeats; i++) {
122 				result.append(leftOperand);
123 			}
124 			return new TypedValue(result.toString());
125 		}
126 
127 		return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
128 	}
129 
130 	@Override
131 	public boolean isCompilable() {
132 		if (!getLeftOperand().isCompilable()) {
133 			return false;
134 		}
135 		if (this.children.length > 1) {
136 			 if (!getRightOperand().isCompilable()) {
137 				 return false;
138 			 }
139 		}
140 		return (this.exitTypeDescriptor != null);
141 	}
142 	
143 	@Override
144 	public void generateCode(MethodVisitor mv, CodeFlow cf) {
145 		getLeftOperand().generateCode(mv, cf);
146 		String leftDesc = getLeftOperand().exitTypeDescriptor;
147 		if (!CodeFlow.isPrimitive(leftDesc)) {
148 			CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), leftDesc);
149 		}
150 		if (this.children.length > 1) {
151 			cf.enterCompilationScope();
152 			getRightOperand().generateCode(mv, cf);
153 			String rightDesc = getRightOperand().exitTypeDescriptor;
154 			cf.exitCompilationScope();
155 			if (!CodeFlow.isPrimitive(rightDesc)) {
156 				CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), rightDesc);
157 			}
158 			switch (this.exitTypeDescriptor.charAt(0)) {
159 				case 'I':
160 					mv.visitInsn(IMUL);
161 					break;
162 				case 'J':
163 					mv.visitInsn(LMUL);
164 					break;
165 				case 'F': 
166 					mv.visitInsn(FMUL);
167 					break;
168 				case 'D':
169 					mv.visitInsn(DMUL);
170 					break;				
171 				default:
172 					throw new IllegalStateException(
173 							"Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
174 			}
175 		}
176 		cf.pushDescriptor(this.exitTypeDescriptor);
177 	}
178 
179 }