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