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;
021import java.math.RoundingMode;
022
023import org.springframework.asm.MethodVisitor;
024import org.springframework.expression.EvaluationException;
025import org.springframework.expression.Operation;
026import org.springframework.expression.TypedValue;
027import org.springframework.expression.spel.CodeFlow;
028import org.springframework.expression.spel.ExpressionState;
029import org.springframework.util.Assert;
030import org.springframework.util.NumberUtils;
031
032/**
033 * Implements division operator.
034 *
035 * @author Andy Clement
036 * @author Juergen Hoeller
037 * @author Giovanni Dall'Oglio Risso
038 * @since 3.0
039 */
040public class OpDivide extends Operator {
041
042        public OpDivide(int startPos, int endPos, SpelNodeImpl... operands) {
043                super("/", startPos, endPos, operands);
044        }
045
046
047        @Override
048        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
049                Object leftOperand = getLeftOperand().getValueInternal(state).getValue();
050                Object rightOperand = getRightOperand().getValueInternal(state).getValue();
051
052                if (leftOperand instanceof Number && rightOperand instanceof Number) {
053                        Number leftNumber = (Number) leftOperand;
054                        Number rightNumber = (Number) rightOperand;
055
056                        if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
057                                BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
058                                BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
059                                int scale = Math.max(leftBigDecimal.scale(), rightBigDecimal.scale());
060                                return new TypedValue(leftBigDecimal.divide(rightBigDecimal, scale, RoundingMode.HALF_EVEN));
061                        }
062                        else if (leftNumber instanceof Double || rightNumber instanceof Double) {
063                                this.exitTypeDescriptor = "D";
064                                return new TypedValue(leftNumber.doubleValue() / rightNumber.doubleValue());
065                        }
066                        else if (leftNumber instanceof Float || rightNumber instanceof Float) {
067                                this.exitTypeDescriptor = "F";
068                                return new TypedValue(leftNumber.floatValue() / rightNumber.floatValue());
069                        }
070                        else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
071                                BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
072                                BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
073                                return new TypedValue(leftBigInteger.divide(rightBigInteger));
074                        }
075                        else if (leftNumber instanceof Long || rightNumber instanceof Long) {
076                                this.exitTypeDescriptor = "J";
077                                return new TypedValue(leftNumber.longValue() / rightNumber.longValue());
078                        }
079                        else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNumericOp(rightNumber)) {
080                                this.exitTypeDescriptor = "I";
081                                return new TypedValue(leftNumber.intValue() / rightNumber.intValue());
082                        }
083                        else {
084                                // Unknown Number subtypes -> best guess is double division
085                                return new TypedValue(leftNumber.doubleValue() / rightNumber.doubleValue());
086                        }
087                }
088
089                return state.operate(Operation.DIVIDE, leftOperand, rightOperand);
090        }
091
092        @Override
093        public boolean isCompilable() {
094                if (!getLeftOperand().isCompilable()) {
095                        return false;
096                }
097                if (this.children.length > 1) {
098                        if (!getRightOperand().isCompilable()) {
099                                return false;
100                        }
101                }
102                return (this.exitTypeDescriptor != null);
103        }
104
105        @Override
106        public void generateCode(MethodVisitor mv, CodeFlow cf) {
107                getLeftOperand().generateCode(mv, cf);
108                String leftDesc = getLeftOperand().exitTypeDescriptor;
109                String exitDesc = this.exitTypeDescriptor;
110                Assert.state(exitDesc != null, "No exit type descriptor");
111                char targetDesc = exitDesc.charAt(0);
112                CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
113                if (this.children.length > 1) {
114                        cf.enterCompilationScope();
115                        getRightOperand().generateCode(mv, cf);
116                        String rightDesc = getRightOperand().exitTypeDescriptor;
117                        cf.exitCompilationScope();
118                        CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
119                        switch (targetDesc) {
120                                case 'I':
121                                        mv.visitInsn(IDIV);
122                                        break;
123                                case 'J':
124                                        mv.visitInsn(LDIV);
125                                        break;
126                                case 'F':
127                                        mv.visitInsn(FDIV);
128                                        break;
129                                case 'D':
130                                        mv.visitInsn(DDIV);
131                                        break;
132                                default:
133                                        throw new IllegalStateException(
134                                                        "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'");
135                        }
136                }
137                cf.pushDescriptor(this.exitTypeDescriptor);
138        }
139
140}