001/*
002 * Copyright 2002-2015 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.NumberUtils;
029
030/**
031 * The minus operator supports:
032 * <ul>
033 * <li>subtraction of numbers
034 * <li>subtraction of an int from a string of one character
035 * (effectively decreasing that character), so 'd'-3='a'
036 * </ul>
037 *
038 * <p>It can be used as a unary operator for numbers.
039 * The standard promotions are performed when the operand types vary (double-int=double).
040 * For other options it defers to the registered overloader.
041 *
042 * @author Andy Clement
043 * @author Juergen Hoeller
044 * @author Giovanni Dall'Oglio Risso
045 * @since 3.0
046 */
047public class OpMinus extends Operator {
048
049        public OpMinus(int pos, SpelNodeImpl... operands) {
050                super("-", pos, operands);
051        }
052
053
054        @Override
055        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
056                SpelNodeImpl leftOp = getLeftOperand();
057                SpelNodeImpl rightOp = getRightOperand();
058
059                if (rightOp == null) {  // 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 = rightOp.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 (getRightOperand() == null) {  // 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                        return null;
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                CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
181                if (this.children.length > 1) {
182                        cf.enterCompilationScope();
183                        getRightOperand().generateCode(mv, cf);
184                        String rightDesc = getRightOperand().exitTypeDescriptor;
185                        cf.exitCompilationScope();
186                        CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
187                        switch (this.exitTypeDescriptor.charAt(0)) {
188                                case 'I':
189                                        mv.visitInsn(ISUB);
190                                        break;
191                                case 'J':
192                                        mv.visitInsn(LSUB);
193                                        break;
194                                case 'F':
195                                        mv.visitInsn(FSUB);
196                                        break;
197                                case 'D':
198                                        mv.visitInsn(DSUB);
199                                        break;
200                                default:
201                                        throw new IllegalStateException(
202                                                        "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
203                        }
204                }
205                else {
206                        switch (this.exitTypeDescriptor.charAt(0)) {
207                                case 'I':
208                                        mv.visitInsn(INEG);
209                                        break;
210                                case 'J':
211                                        mv.visitInsn(LNEG);
212                                        break;
213                                case 'F':
214                                        mv.visitInsn(FNEG);
215                                        break;
216                                case 'D':
217                                        mv.visitInsn(DNEG);
218                                        break;
219                                default:
220                                        throw new IllegalStateException(
221                                                        "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
222                        }
223                }
224                cf.pushDescriptor(this.exitTypeDescriptor);
225        }
226
227}