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 org.springframework.asm.MethodVisitor;
020import org.springframework.asm.Type;
021import org.springframework.expression.EvaluationException;
022import org.springframework.expression.TypedValue;
023import org.springframework.expression.spel.CodeFlow;
024import org.springframework.expression.spel.ExpressionState;
025import org.springframework.expression.spel.SpelEvaluationException;
026import org.springframework.expression.spel.SpelMessage;
027import org.springframework.expression.spel.support.BooleanTypedValue;
028import org.springframework.lang.Nullable;
029import org.springframework.util.Assert;
030
031/**
032 * The operator 'instanceof' checks if an object is of the class specified in the right
033 * hand operand, in the same way that {@code instanceof} does in Java.
034 *
035 * @author Andy Clement
036 * @since 3.0
037 */
038public class OperatorInstanceof extends Operator {
039
040        @Nullable
041        private Class<?> type;
042
043
044        public OperatorInstanceof(int startPos, int endPos, SpelNodeImpl... operands) {
045                super("instanceof", startPos, endPos, operands);
046        }
047
048
049        /**
050         * Compare the left operand to see it is an instance of the type specified as the
051         * right operand. The right operand must be a class.
052         * @param state the expression state
053         * @return {@code true} if the left operand is an instanceof of the right operand,
054         * otherwise {@code false}
055         * @throws EvaluationException if there is a problem evaluating the expression
056         */
057        @Override
058        public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
059                SpelNodeImpl rightOperand = getRightOperand();
060                TypedValue left = getLeftOperand().getValueInternal(state);
061                TypedValue right = rightOperand.getValueInternal(state);
062                Object leftValue = left.getValue();
063                Object rightValue = right.getValue();
064                BooleanTypedValue result;
065                if (rightValue == null || !(rightValue instanceof Class)) {
066                        throw new SpelEvaluationException(getRightOperand().getStartPosition(),
067                                        SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND,
068                                        (rightValue == null ? "null" : rightValue.getClass().getName()));
069                }
070                Class<?> rightClass = (Class<?>) rightValue;
071                if (leftValue == null) {
072                        result = BooleanTypedValue.FALSE;  // null is not an instanceof anything
073                }
074                else {
075                        result = BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass()));
076                }
077                this.type = rightClass;
078                if (rightOperand instanceof TypeReference) {
079                        // Can only generate bytecode where the right operand is a direct type reference,
080                        // not if it is indirect (for example when right operand is a variable reference)
081                        this.exitTypeDescriptor = "Z";
082                }
083                return result;
084        }
085
086        @Override
087        public boolean isCompilable() {
088                return (this.exitTypeDescriptor != null && getLeftOperand().isCompilable());
089        }
090
091        @Override
092        public void generateCode(MethodVisitor mv, CodeFlow cf) {
093                getLeftOperand().generateCode(mv, cf);
094                CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor());
095                Assert.state(this.type != null, "No type available");
096                if (this.type.isPrimitive()) {
097                        // always false - but left operand code always driven
098                        // in case it had side effects
099                        mv.visitInsn(POP);
100                        mv.visitInsn(ICONST_0); // value of false
101                }
102                else {
103                        mv.visitTypeInsn(INSTANCEOF, Type.getInternalName(this.type));
104                }
105                cf.pushDescriptor(this.exitTypeDescriptor);
106        }
107
108}