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