001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.ast;
018
019import java.math.BigDecimal;
020import java.math.BigInteger;
021
022import org.springframework.asm.MethodVisitor;
023import org.springframework.core.convert.TypeDescriptor;
024import org.springframework.expression.EvaluationException;
025import org.springframework.expression.Operation;
026import org.springframework.expression.TypeConverter;
027import org.springframework.expression.TypedValue;
028import org.springframework.expression.spel.CodeFlow;
029import org.springframework.expression.spel.ExpressionState;
030import org.springframework.util.Assert;
031import org.springframework.util.NumberUtils;
032
033/**
034 * The plus operator will:
035 * <ul>
036 * <li>add numbers
037 * <li>concatenate strings
038 * </ul>
039 *
040 * <p>It can be used as a unary operator for numbers.
041 * The standard promotions are performed when the operand types vary (double+int=double).
042 * For other options it defers to the registered overloader.
043 *
044 * @author Andy Clement
045 * @author Juergen Hoeller
046 * @author Ivo Smid
047 * @author Giovanni Dall'Oglio Risso
048 * @since 3.0
049 */
050public class OpPlus extends Operator {
051
052        public OpPlus(int pos, SpelNodeImpl... operands) {
053                super("+", pos, operands);
054                Assert.notEmpty(operands, "Operands must not be empty");
055        }
056
057
058        @Override
059        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
060                SpelNodeImpl leftOp = getLeftOperand();
061                SpelNodeImpl rightOp = getRightOperand();
062
063                if (rightOp == null) {  // if only one operand, then this is unary plus
064                        Object operandOne = leftOp.getValueInternal(state).getValue();
065                        if (operandOne instanceof Number) {
066                                if (operandOne instanceof Double) {
067                                        this.exitTypeDescriptor = "D";
068                                }
069                                else if (operandOne instanceof Float) {
070                                        this.exitTypeDescriptor = "F";
071                                }
072                                else if (operandOne instanceof Long) {
073                                        this.exitTypeDescriptor = "J";
074                                }
075                                else if (operandOne instanceof Integer) {
076                                        this.exitTypeDescriptor = "I";
077                                }
078                                return new TypedValue(operandOne);
079                        }
080                        return state.operate(Operation.ADD, operandOne, null);
081                }
082
083                TypedValue operandOneValue = leftOp.getValueInternal(state);
084                Object leftOperand = operandOneValue.getValue();
085                TypedValue operandTwoValue = rightOp.getValueInternal(state);
086                Object rightOperand = operandTwoValue.getValue();
087
088                if (leftOperand instanceof Number && rightOperand instanceof Number) {
089                        Number leftNumber = (Number) leftOperand;
090                        Number rightNumber = (Number) rightOperand;
091
092                        if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
093                                BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
094                                BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
095                                return new TypedValue(leftBigDecimal.add(rightBigDecimal));
096                        }
097                        else if (leftNumber instanceof Double || rightNumber instanceof Double) {
098                                this.exitTypeDescriptor = "D";
099                                return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
100                        }
101                        else if (leftNumber instanceof Float || rightNumber instanceof Float) {
102                                this.exitTypeDescriptor = "F";
103                                return new TypedValue(leftNumber.floatValue() + rightNumber.floatValue());
104                        }
105                        else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
106                                BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
107                                BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
108                                return new TypedValue(leftBigInteger.add(rightBigInteger));
109                        }
110                        else if (leftNumber instanceof Long || rightNumber instanceof Long) {
111                                this.exitTypeDescriptor = "J";
112                                return new TypedValue(leftNumber.longValue() + rightNumber.longValue());
113                        }
114                        else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNumericOp(rightNumber)) {
115                                this.exitTypeDescriptor = "I";
116                                return new TypedValue(leftNumber.intValue() + rightNumber.intValue());
117                        }
118                        else {
119                                // Unknown Number subtypes -> best guess is double addition
120                                return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
121                        }
122                }
123
124                if (leftOperand instanceof String && rightOperand instanceof String) {
125                        this.exitTypeDescriptor = "Ljava/lang/String";
126                        return new TypedValue((String) leftOperand + rightOperand);
127                }
128
129                if (leftOperand instanceof String) {
130                        return new TypedValue(
131                                        leftOperand + (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state)));
132                }
133
134                if (rightOperand instanceof String) {
135                        return new TypedValue(
136                                        (leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state)) + rightOperand);
137                }
138
139                return state.operate(Operation.ADD, leftOperand, rightOperand);
140        }
141
142        @Override
143        public String toStringAST() {
144                if (this.children.length < 2) {  // unary plus
145                        return "+" + getLeftOperand().toStringAST();
146                }
147                return super.toStringAST();
148        }
149
150        @Override
151        public SpelNodeImpl getRightOperand() {
152                if (this.children.length < 2) {
153                        return null;
154                }
155                return this.children[1];
156        }
157
158        /**
159         * Convert operand value to string using registered converter or using
160         * {@code toString} method.
161         * @param value typed value to be converted
162         * @param state expression state
163         * @return {@code TypedValue} instance converted to {@code String}
164         */
165        private static String convertTypedValueToString(TypedValue value, ExpressionState state) {
166                TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
167                TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(String.class);
168                if (typeConverter.canConvert(value.getTypeDescriptor(), typeDescriptor)) {
169                        return String.valueOf(typeConverter.convertValue(value.getValue(),
170                                        value.getTypeDescriptor(), typeDescriptor));
171                }
172                return String.valueOf(value.getValue());
173        }
174
175        @Override
176        public boolean isCompilable() {
177                if (!getLeftOperand().isCompilable()) {
178                        return false;
179                }
180                if (this.children.length > 1) {
181                         if (!getRightOperand().isCompilable()) {
182                                 return false;
183                         }
184                }
185                return (this.exitTypeDescriptor != null);
186        }
187
188        /**
189         * Walk through a possible tree of nodes that combine strings and append
190         * them all to the same (on stack) StringBuilder.
191         */
192        private void walk(MethodVisitor mv, CodeFlow cf, SpelNodeImpl operand) {
193                if (operand instanceof OpPlus) {
194                        OpPlus plus = (OpPlus)operand;
195                        walk(mv, cf, plus.getLeftOperand());
196                        walk(mv, cf, plus.getRightOperand());
197                }
198                else {
199                        cf.enterCompilationScope();
200                        operand.generateCode(mv,cf);
201                        if (!"Ljava/lang/String".equals(cf.lastDescriptor())) {
202                                mv.visitTypeInsn(CHECKCAST, "java/lang/String");
203                        }
204                        cf.exitCompilationScope();
205                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
206                }
207        }
208        
209        @Override
210        public void generateCode(MethodVisitor mv, CodeFlow cf) {
211                if ("Ljava/lang/String".equals(this.exitTypeDescriptor)) {
212                        mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
213                        mv.visitInsn(DUP);
214                        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
215                        walk(mv,cf,getLeftOperand());
216                        walk(mv,cf,getRightOperand());
217                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
218                }
219                else {
220                        getLeftOperand().generateCode(mv, cf);
221                        String leftDesc = getLeftOperand().exitTypeDescriptor;
222                        CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
223                        if (this.children.length > 1) {
224                                cf.enterCompilationScope();
225                                getRightOperand().generateCode(mv, cf);
226                                String rightDesc = getRightOperand().exitTypeDescriptor;
227                                cf.exitCompilationScope();
228                                CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
229                                switch (this.exitTypeDescriptor.charAt(0)) {
230                                        case 'I':
231                                                mv.visitInsn(IADD);
232                                                break;
233                                        case 'J':
234                                                mv.visitInsn(LADD);
235                                                break;
236                                        case 'F':
237                                                mv.visitInsn(FADD);
238                                                break;
239                                        case 'D':
240                                                mv.visitInsn(DADD);
241                                                break;
242                                        default:
243                                                throw new IllegalStateException(
244                                                                "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
245                                }
246                        }
247                }
248                cf.pushDescriptor(this.exitTypeDescriptor);
249        }
250
251}