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}