View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel.ast;
18  
19  import org.springframework.asm.Label;
20  import org.springframework.asm.MethodVisitor;
21  import org.springframework.expression.EvaluationException;
22  import org.springframework.expression.TypedValue;
23  import org.springframework.expression.spel.CodeFlow;
24  import org.springframework.expression.spel.ExpressionState;
25  import org.springframework.util.StringUtils;
26  
27  /**
28   * Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value
29   * of the expression is "a", if a is null then the value of the expression is "b".
30   *
31   * @author Andy Clement
32   * @author Juergen Hoeller
33   * @since 3.0
34   */
35  public class Elvis extends SpelNodeImpl {
36  
37  	public Elvis(int pos, SpelNodeImpl... args) {
38  		super(pos,args);
39  	}
40  
41  
42  	/**
43  	 * Evaluate the condition and if not null, return it.
44  	 * If it is null, return the other value.
45  	 * @param state the expression state
46  	 * @throws EvaluationException if the condition does not evaluate correctly
47  	 * to a boolean or there is a problem executing the chosen alternative
48  	 */
49  	@Override
50  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
51  		TypedValue value = this.children[0].getValueInternal(state);
52  		if (!StringUtils.isEmpty(value.getValue())) {
53  			return value;
54  		}
55  		else {
56  			TypedValue result = this.children[1].getValueInternal(state);
57  			computeExitTypeDescriptor();
58  			return result;
59  		}
60  	}
61  
62  	@Override
63  	public String toStringAST() {
64  		return getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST();
65  	}
66  
67  	@Override
68  	public boolean isCompilable() {
69  		SpelNodeImpl condition = this.children[0];
70  		SpelNodeImpl ifNullValue = this.children[1];
71  		return (condition.isCompilable() && ifNullValue.isCompilable() &&
72  				condition.exitTypeDescriptor != null && ifNullValue.exitTypeDescriptor != null);
73  	}
74  
75  	@Override
76  	public void generateCode(MethodVisitor mv, CodeFlow cf) {
77  		// exit type descriptor can be null if both components are literal expressions
78  		computeExitTypeDescriptor();
79  		this.children[0].generateCode(mv, cf);
80  		Label elseTarget = new Label();
81  		Label endOfIf = new Label();
82  		mv.visitInsn(DUP);
83  		mv.visitJumpInsn(IFNULL, elseTarget);
84  		mv.visitJumpInsn(GOTO, endOfIf);
85  		mv.visitLabel(elseTarget);
86  		mv.visitInsn(POP);
87  		this.children[1].generateCode(mv, cf);
88  		if (!CodeFlow.isPrimitive(this.exitTypeDescriptor)) {
89  			CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor().charAt(0));
90  		}
91  		mv.visitLabel(endOfIf);
92  		cf.pushDescriptor(this.exitTypeDescriptor);
93  	}
94  
95  	private void computeExitTypeDescriptor() {
96  		if (this.exitTypeDescriptor == null && this.children[0].exitTypeDescriptor != null &&
97  				this.children[1].exitTypeDescriptor != null) {
98  			String conditionDescriptor = this.children[0].exitTypeDescriptor;
99  			String ifNullValueDescriptor = this.children[1].exitTypeDescriptor;
100 			if (conditionDescriptor.equals(ifNullValueDescriptor)) {
101 				this.exitTypeDescriptor = conditionDescriptor;
102 			}
103 			else if (conditionDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(ifNullValueDescriptor)) {
104 				this.exitTypeDescriptor = ifNullValueDescriptor;
105 			}
106 			else if (ifNullValueDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(conditionDescriptor)) {
107 				this.exitTypeDescriptor = conditionDescriptor;
108 			}
109 			else {
110 				// Use the easiest to compute common super type
111 				this.exitTypeDescriptor = "Ljava/lang/Object";
112 			}
113 		}
114 	}
115 
116 }