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.expression.spel.SpelEvaluationException;
026import org.springframework.expression.spel.SpelMessage;
027import org.springframework.util.Assert;
028import org.springframework.util.ObjectUtils;
029
030/**
031 * Represents a ternary expression, for example: "someCheck()?true:false".
032 *
033 * @author Andy Clement
034 * @author Juergen Hoeller
035 * @since 3.0
036 */
037public class Ternary extends SpelNodeImpl {
038
039        public Ternary(int startPos, int endPos, SpelNodeImpl... args) {
040                super(startPos, endPos, args);
041        }
042
043
044        /**
045         * Evaluate the condition and if true evaluate the first alternative, otherwise
046         * evaluate the second alternative.
047         * @param state the expression state
048         * @throws EvaluationException if the condition does not evaluate correctly to
049         * a boolean or there is a problem executing the chosen alternative
050         */
051        @Override
052        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
053                Boolean value = this.children[0].getValue(state, Boolean.class);
054                if (value == null) {
055                        throw new SpelEvaluationException(getChild(0).getStartPosition(),
056                                        SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
057                }
058                TypedValue result = this.children[value ? 1 : 2].getValueInternal(state);
059                computeExitTypeDescriptor();
060                return result;
061        }
062
063        @Override
064        public String toStringAST() {
065                return getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST();
066        }
067
068        private void computeExitTypeDescriptor() {
069                if (this.exitTypeDescriptor == null && this.children[1].exitTypeDescriptor != null &&
070                                this.children[2].exitTypeDescriptor != null) {
071                        String leftDescriptor = this.children[1].exitTypeDescriptor;
072                        String rightDescriptor = this.children[2].exitTypeDescriptor;
073                        if (ObjectUtils.nullSafeEquals(leftDescriptor, rightDescriptor)) {
074                                this.exitTypeDescriptor = leftDescriptor;
075                        }
076                        else {
077                                // Use the easiest to compute common super type
078                                this.exitTypeDescriptor = "Ljava/lang/Object";
079                        }
080                }
081        }
082
083        @Override
084        public boolean isCompilable() {
085                SpelNodeImpl condition = this.children[0];
086                SpelNodeImpl left = this.children[1];
087                SpelNodeImpl right = this.children[2];
088                return (condition.isCompilable() && left.isCompilable() && right.isCompilable() &&
089                                CodeFlow.isBooleanCompatible(condition.exitTypeDescriptor) &&
090                                left.exitTypeDescriptor != null && right.exitTypeDescriptor != null);
091        }
092
093        @Override
094        public void generateCode(MethodVisitor mv, CodeFlow cf) {
095                // May reach here without it computed if all elements are literals
096                computeExitTypeDescriptor();
097                cf.enterCompilationScope();
098                this.children[0].generateCode(mv, cf);
099                String lastDesc = cf.lastDescriptor();
100                Assert.state(lastDesc != null, "No last descriptor");
101                if (!CodeFlow.isPrimitive(lastDesc)) {
102                        CodeFlow.insertUnboxInsns(mv, 'Z', lastDesc);
103                }
104                cf.exitCompilationScope();
105                Label elseTarget = new Label();
106                Label endOfIf = new Label();
107                mv.visitJumpInsn(IFEQ, elseTarget);
108                cf.enterCompilationScope();
109                this.children[1].generateCode(mv, cf);
110                if (!CodeFlow.isPrimitive(this.exitTypeDescriptor)) {
111                        lastDesc = cf.lastDescriptor();
112                        Assert.state(lastDesc != null, "No last descriptor");
113                        CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
114                }
115                cf.exitCompilationScope();
116                mv.visitJumpInsn(GOTO, endOfIf);
117                mv.visitLabel(elseTarget);
118                cf.enterCompilationScope();
119                this.children[2].generateCode(mv, cf);
120                if (!CodeFlow.isPrimitive(this.exitTypeDescriptor)) {
121                        lastDesc = cf.lastDescriptor();
122                        Assert.state(lastDesc != null, "No last descriptor");
123                        CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
124                }
125                cf.exitCompilationScope();
126                mv.visitLabel(endOfIf);
127                cf.pushDescriptor(this.exitTypeDescriptor);
128        }
129
130}