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}