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.Label;
020import org.springframework.asm.MethodVisitor;
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.util.Assert;
026import org.springframework.util.ObjectUtils;
027import org.springframework.util.StringUtils;
028
029/**
030 * Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value
031 * of the expression is "a", if a is null then the value of the expression is "b".
032 *
033 * @author Andy Clement
034 * @author Juergen Hoeller
035 * @since 3.0
036 */
037public class Elvis extends SpelNodeImpl {
038
039        public Elvis(int startPos, int endPos, SpelNodeImpl... args) {
040                super(startPos, endPos, args);
041        }
042
043
044        /**
045         * Evaluate the condition and if not null, return it.
046         * If it is null, return the other value.
047         * @param state the expression state
048         * @throws EvaluationException if the condition does not evaluate correctly
049         * to a boolean or there is a problem executing the chosen alternative
050         */
051        @Override
052        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
053                TypedValue value = this.children[0].getValueInternal(state);
054                // If this check is changed, the generateCode method will need changing too
055                if (!StringUtils.isEmpty(value.getValue())) {
056                        return value;
057                }
058                else {
059                        TypedValue result = this.children[1].getValueInternal(state);
060                        computeExitTypeDescriptor();
061                        return result;
062                }
063        }
064
065        @Override
066        public String toStringAST() {
067                return getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST();
068        }
069
070        @Override
071        public boolean isCompilable() {
072                SpelNodeImpl condition = this.children[0];
073                SpelNodeImpl ifNullValue = this.children[1];
074                return (condition.isCompilable() && ifNullValue.isCompilable() &&
075                                condition.exitTypeDescriptor != null && ifNullValue.exitTypeDescriptor != null);
076        }
077
078        @Override
079        public void generateCode(MethodVisitor mv, CodeFlow cf) {
080                // exit type descriptor can be null if both components are literal expressions
081                computeExitTypeDescriptor();
082                cf.enterCompilationScope();
083                this.children[0].generateCode(mv, cf);
084                String lastDesc = cf.lastDescriptor();
085                Assert.state(lastDesc != null, "No last descriptor");
086                CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
087                cf.exitCompilationScope();
088                Label elseTarget = new Label();
089                Label endOfIf = new Label();
090                mv.visitInsn(DUP);
091                mv.visitJumpInsn(IFNULL, elseTarget);
092                // Also check if empty string, as per the code in the interpreted version
093                mv.visitInsn(DUP);
094                mv.visitLdcInsn("");
095                mv.visitInsn(SWAP);
096                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z",false);
097                mv.visitJumpInsn(IFEQ, endOfIf);  // if not empty, drop through to elseTarget
098                mv.visitLabel(elseTarget);
099                mv.visitInsn(POP);
100                cf.enterCompilationScope();
101                this.children[1].generateCode(mv, cf);
102                if (!CodeFlow.isPrimitive(this.exitTypeDescriptor)) {
103                        lastDesc = cf.lastDescriptor();
104                        Assert.state(lastDesc != null, "No last descriptor");
105                        CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
106                }
107                cf.exitCompilationScope();
108                mv.visitLabel(endOfIf);
109                cf.pushDescriptor(this.exitTypeDescriptor);
110        }
111
112        private void computeExitTypeDescriptor() {
113                if (this.exitTypeDescriptor == null && this.children[0].exitTypeDescriptor != null &&
114                                this.children[1].exitTypeDescriptor != null) {
115                        String conditionDescriptor = this.children[0].exitTypeDescriptor;
116                        String ifNullValueDescriptor = this.children[1].exitTypeDescriptor;
117                        if (ObjectUtils.nullSafeEquals(conditionDescriptor, ifNullValueDescriptor)) {
118                                this.exitTypeDescriptor = conditionDescriptor;
119                        }
120                        else {
121                                // Use the easiest to compute common super type
122                                this.exitTypeDescriptor = "Ljava/lang/Object";
123                        }
124                }
125        }
126
127}