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