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}