001/*
002 * Copyright 2002-2019 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.expression.EvaluationException;
024import org.springframework.expression.Operation;
025import org.springframework.expression.TypedValue;
026import org.springframework.expression.spel.CodeFlow;
027import org.springframework.expression.spel.ExpressionState;
028import org.springframework.util.Assert;
029import org.springframework.util.NumberUtils;
030
031/**
032 * The minus operator supports:
033 * <ul>
034 * <li>subtraction of numbers
035 * <li>subtraction of an int from a string of one character
036 * (effectively decreasing that character), so 'd'-3='a'
037 * </ul>
038 *
039 * <p>It can be used as a unary operator for numbers.
040 * The standard promotions are performed when the operand types vary (double-int=double).
041 * For other options it defers to the registered overloader.
042 *
043 * @author Andy Clement
044 * @author Juergen Hoeller
045 * @author Giovanni Dall'Oglio Risso
046 * @since 3.0
047 */
048public class OpMinus extends Operator {
049
050        public OpMinus(int startPos, int endPos, SpelNodeImpl... operands) {
051                super("-", startPos, endPos, operands);
052        }
053
054
055        @Override
056        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
057                SpelNodeImpl leftOp = getLeftOperand();
058
059                if (this.children.length < 2) {  // if only one operand, then this is unary minus
060                        Object operand = leftOp.getValueInternal(state).getValue();
061                        if (operand instanceof Number) {
062                                if (operand instanceof BigDecimal) {
063                                        return new TypedValue(((BigDecimal) operand).negate());
064                                }
065                                else if (operand instanceof Double) {
066                                        this.exitTypeDescriptor = "D";
067                                        return new TypedValue(0 - ((Number) operand).doubleValue());
068                                }
069                                else if (operand instanceof Float) {
070                                        this.exitTypeDescriptor = "F";
071                                        return new TypedValue(0 - ((Number) operand).floatValue());
072                                }
073                                else if (operand instanceof BigInteger) {
074                                        return new TypedValue(((BigInteger) operand).negate());
075                                }
076                                else if (operand instanceof Long) {
077                                        this.exitTypeDescriptor = "J";
078                                        return new TypedValue(0 - ((Number) operand).longValue());
079                                }
080                                else if (operand instanceof Integer) {
081                                        this.exitTypeDescriptor = "I";
082                                        return new TypedValue(0 - ((Number) operand).intValue());
083                                }
084                                else if (operand instanceof Short) {
085                                        return new TypedValue(0 - ((Number) operand).shortValue());
086                                }
087                                else if (operand instanceof Byte) {
088                                        return new TypedValue(0 - ((Number) operand).byteValue());
089                                }
090                                else {
091                                        // Unknown Number subtypes -> best guess is double subtraction
092                                        return new TypedValue(0 - ((Number) operand).doubleValue());
093                                }
094                        }
095                        return state.operate(Operation.SUBTRACT, operand, null);
096                }
097
098                Object left = leftOp.getValueInternal(state).getValue();
099                Object right = getRightOperand().getValueInternal(state).getValue();
100
101                if (left instanceof Number && right instanceof Number) {
102                        Number leftNumber = (Number) left;
103                        Number rightNumber = (Number) right;
104
105                        if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
106                                BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
107                                BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
108                                return new TypedValue(leftBigDecimal.subtract(rightBigDecimal));
109                        }
110                        else if (leftNumber instanceof Double || rightNumber instanceof Double) {
111                                this.exitTypeDescriptor = "D";
112                                return new TypedValue(leftNumber.doubleValue() - rightNumber.doubleValue());
113                        }
114                        else if (leftNumber instanceof Float || rightNumber instanceof Float) {
115                                this.exitTypeDescriptor = "F";
116                                return new TypedValue(leftNumber.floatValue() - rightNumber.floatValue());
117                        }
118                        else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
119                                BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
120                                BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
121                                return new TypedValue(leftBigInteger.subtract(rightBigInteger));
122                        }
123                        else if (leftNumber instanceof Long || rightNumber instanceof Long) {
124                                this.exitTypeDescriptor = "J";
125                                return new TypedValue(leftNumber.longValue() - rightNumber.longValue());
126                        }
127                        else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNumericOp(rightNumber)) {
128                                this.exitTypeDescriptor = "I";
129                                return new TypedValue(leftNumber.intValue() - rightNumber.intValue());
130                        }
131                        else {
132                                // Unknown Number subtypes -> best guess is double subtraction
133                                return new TypedValue(leftNumber.doubleValue() - rightNumber.doubleValue());
134                        }
135                }
136
137                if (left instanceof String && right instanceof Integer && ((String) left).length() == 1) {
138                        String theString = (String) left;
139                        Integer theInteger = (Integer) right;
140                        // Implements character - int (ie. b - 1 = a)
141                        return new TypedValue(Character.toString((char) (theString.charAt(0) - theInteger)));
142                }
143
144                return state.operate(Operation.SUBTRACT, left, right);
145        }
146
147        @Override
148        public String toStringAST() {
149                if (this.children.length < 2) {  // unary minus
150                        return "-" + getLeftOperand().toStringAST();
151                }
152                return super.toStringAST();
153        }
154
155        @Override
156        public SpelNodeImpl getRightOperand() {
157                if (this.children.length < 2) {
158                        throw new IllegalStateException("No right operand");
159                }
160                return this.children[1];
161        }
162
163        @Override
164        public boolean isCompilable() {
165                if (!getLeftOperand().isCompilable()) {
166                        return false;
167                }
168                if (this.children.length > 1) {
169                        if (!getRightOperand().isCompilable()) {
170                                return false;
171                        }
172                }
173                return (this.exitTypeDescriptor != null);
174        }
175
176        @Override
177        public void generateCode(MethodVisitor mv, CodeFlow cf) {
178                getLeftOperand().generateCode(mv, cf);
179                String leftDesc = getLeftOperand().exitTypeDescriptor;
180                String exitDesc = this.exitTypeDescriptor;
181                Assert.state(exitDesc != null, "No exit type descriptor");
182                char targetDesc = exitDesc.charAt(0);
183                CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
184                if (this.children.length > 1) {
185                        cf.enterCompilationScope();
186                        getRightOperand().generateCode(mv, cf);
187                        String rightDesc = getRightOperand().exitTypeDescriptor;
188                        cf.exitCompilationScope();
189                        CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
190                        switch (targetDesc) {
191                                case 'I':
192                                        mv.visitInsn(ISUB);
193                                        break;
194                                case 'J':
195                                        mv.visitInsn(LSUB);
196                                        break;
197                                case 'F':
198                                        mv.visitInsn(FSUB);
199                                        break;
200                                case 'D':
201                                        mv.visitInsn(DSUB);
202                                        break;
203                                default:
204                                        throw new IllegalStateException(
205                                                        "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
206                        }
207                }
208                else {
209                        switch (targetDesc) {
210                                case 'I':
211                                        mv.visitInsn(INEG);
212                                        break;
213                                case 'J':
214                                        mv.visitInsn(LNEG);
215                                        break;
216                                case 'F':
217                                        mv.visitInsn(FNEG);
218                                        break;
219                                case 'D':
220                                        mv.visitInsn(DNEG);
221                                        break;
222                                default:
223                                        throw new IllegalStateException(
224                                                        "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
225                        }
226                }
227                cf.pushDescriptor(this.exitTypeDescriptor);
228        }
229
230}