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