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 java.lang.reflect.Modifier; 020 021import org.springframework.asm.MethodVisitor; 022import org.springframework.expression.EvaluationContext; 023import org.springframework.expression.TypedValue; 024import org.springframework.expression.spel.CodeFlow; 025import org.springframework.expression.spel.ExpressionState; 026import org.springframework.expression.spel.SpelEvaluationException; 027import org.springframework.lang.Nullable; 028 029/** 030 * Represents a variable reference, eg. #someVar. Note this is different to a *local* 031 * variable like $someVar 032 * 033 * @author Andy Clement 034 * @since 3.0 035 */ 036public class VariableReference extends SpelNodeImpl { 037 038 // Well known variables: 039 private static final String THIS = "this"; // currently active context object 040 041 private static final String ROOT = "root"; // root context object 042 043 044 private final String name; 045 046 047 public VariableReference(String variableName, int startPos, int endPos) { 048 super(startPos, endPos); 049 this.name = variableName; 050 } 051 052 053 @Override 054 public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException { 055 if (this.name.equals(THIS)) { 056 return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(),this); 057 } 058 if (this.name.equals(ROOT)) { 059 return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(),this); 060 } 061 TypedValue result = state.lookupVariable(this.name); 062 // a null value will mean either the value was null or the variable was not found 063 return new VariableRef(this.name,result,state.getEvaluationContext()); 064 } 065 066 @Override 067 public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException { 068 if (this.name.equals(THIS)) { 069 return state.getActiveContextObject(); 070 } 071 if (this.name.equals(ROOT)) { 072 TypedValue result = state.getRootContextObject(); 073 this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue()); 074 return result; 075 } 076 TypedValue result = state.lookupVariable(this.name); 077 Object value = result.getValue(); 078 if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) { 079 // If the type is not public then when generateCode produces a checkcast to it 080 // then an IllegalAccessError will occur. 081 // If resorting to Object isn't sufficient, the hierarchy could be traversed for 082 // the first public type. 083 this.exitTypeDescriptor = "Ljava/lang/Object"; 084 } 085 else { 086 this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value); 087 } 088 // a null value will mean either the value was null or the variable was not found 089 return result; 090 } 091 092 @Override 093 public void setValue(ExpressionState state, @Nullable Object value) throws SpelEvaluationException { 094 state.setVariable(this.name, value); 095 } 096 097 @Override 098 public String toStringAST() { 099 return "#" + this.name; 100 } 101 102 @Override 103 public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException { 104 return !(this.name.equals(THIS) || this.name.equals(ROOT)); 105 } 106 107 @Override 108 public boolean isCompilable() { 109 return (this.exitTypeDescriptor != null); 110 } 111 112 @Override 113 public void generateCode(MethodVisitor mv, CodeFlow cf) { 114 if (this.name.equals(ROOT)) { 115 mv.visitVarInsn(ALOAD,1); 116 } 117 else { 118 mv.visitVarInsn(ALOAD, 2); 119 mv.visitLdcInsn(this.name); 120 mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext", "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;",true); 121 } 122 CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor); 123 cf.pushDescriptor(this.exitTypeDescriptor); 124 } 125 126 127 private static class VariableRef implements ValueRef { 128 129 private final String name; 130 131 private final TypedValue value; 132 133 private final EvaluationContext evaluationContext; 134 135 public VariableRef(String name, TypedValue value, EvaluationContext evaluationContext) { 136 this.name = name; 137 this.value = value; 138 this.evaluationContext = evaluationContext; 139 } 140 141 @Override 142 public TypedValue getValue() { 143 return this.value; 144 } 145 146 @Override 147 public void setValue(@Nullable Object newValue) { 148 this.evaluationContext.setVariable(this.name, newValue); 149 } 150 151 @Override 152 public boolean isWritable() { 153 return true; 154 } 155 } 156 157}