29
29
import org .springframework .lang .Nullable ;
30
30
31
31
/**
32
- * Represents a variable reference — for example, {@code #someVar}.
32
+ * Represents a variable reference — for example, {@code #root}, {@code #this},
33
+ * {@code #someVar}, etc.
33
34
*
34
35
* @author Andy Clement
35
36
* @author Sam Brannen
36
37
* @since 3.0
37
38
*/
38
39
public class VariableReference extends SpelNodeImpl {
39
40
40
- // Well known variables:
41
- private static final String THIS = "this" ; // currently active context object
41
+ /** Currently active context object. */
42
+ private static final String THIS = "this" ;
42
43
43
- private static final String ROOT = "root" ; // root context object
44
+ /** Root context object. */
45
+ private static final String ROOT = "root" ;
44
46
45
47
46
48
private final String name ;
@@ -54,41 +56,56 @@ public VariableReference(String variableName, int startPos, int endPos) {
54
56
55
57
@ Override
56
58
public ValueRef getValueRef (ExpressionState state ) throws SpelEvaluationException {
57
- if (this . name . equals (THIS )) {
59
+ if (THIS . equals (this . name )) {
58
60
return new ValueRef .TypedValueHolderValueRef (state .getActiveContextObject (), this );
59
61
}
60
- if (this . name . equals (ROOT )) {
62
+ if (ROOT . equals (this . name )) {
61
63
return new ValueRef .TypedValueHolderValueRef (state .getRootContextObject (), this );
62
64
}
63
65
TypedValue result = state .lookupVariable (this .name );
64
- // a null value will mean either the value was null or the variable was not found
66
+ // A null value in the returned VariableRef will mean either the value was
67
+ // null or the variable was not found.
65
68
return new VariableRef (this .name , result , state .getEvaluationContext ());
66
69
}
67
70
68
71
@ Override
69
72
public TypedValue getValueInternal (ExpressionState state ) throws SpelEvaluationException {
70
- if (this . name . equals (THIS )) {
73
+ if (THIS . equals (this . name )) {
71
74
return state .getActiveContextObject ();
72
75
}
73
- if (this . name . equals (ROOT )) {
76
+ if (ROOT . equals (this . name )) {
74
77
TypedValue result = state .getRootContextObject ();
75
78
this .exitTypeDescriptor = CodeFlow .toDescriptorFromObject (result .getValue ());
76
79
return result ;
77
80
}
81
+
78
82
TypedValue result = state .lookupVariable (this .name );
79
- Object value = result .getValue ();
83
+ setExitTypeDescriptor (result .getValue ());
84
+
85
+ // A null value in the returned TypedValue will mean either the value was
86
+ // null or the variable was not found.
87
+ return result ;
88
+ }
89
+
90
+ /**
91
+ * Set the exit type descriptor for the supplied value.
92
+ * <p>If the value is {@code null}, we set the exit type descriptor to
93
+ * {@link Object}.
94
+ * <p>If the value's type is not public, {@link #generateCode} would insert
95
+ * a checkcast to the non-public type in the generated byte code which would
96
+ * result in an {@link IllegalAccessError} when the compiled byte code is
97
+ * invoked. Thus, as a preventative measure, we set the exit type descriptor
98
+ * to {@code Object} in such cases. If resorting to {@code Object} is not
99
+ * sufficient, we could consider traversing the hierarchy to find the first
100
+ * public type.
101
+ */
102
+ private void setExitTypeDescriptor (@ Nullable Object value ) {
80
103
if (value == null || !Modifier .isPublic (value .getClass ().getModifiers ())) {
81
- // If the type is not public then when generateCode produces a checkcast to it
82
- // then an IllegalAccessError will occur.
83
- // If resorting to Object isn't sufficient, the hierarchy could be traversed for
84
- // the first public type.
85
104
this .exitTypeDescriptor = "Ljava/lang/Object" ;
86
105
}
87
106
else {
88
107
this .exitTypeDescriptor = CodeFlow .toDescriptorFromObject (value );
89
108
}
90
- // a null value will mean either the value was null or the variable was not found
91
- return result ;
92
109
}
93
110
94
111
@ Override
@@ -105,7 +122,7 @@ public String toStringAST() {
105
122
106
123
@ Override
107
124
public boolean isWritable (ExpressionState expressionState ) throws SpelEvaluationException {
108
- return !(this . name . equals (THIS ) || this . name . equals (ROOT ));
125
+ return !(THIS . equals (this . name ) || ROOT . equals (this . name ));
109
126
}
110
127
111
128
@ Override
@@ -115,13 +132,14 @@ public boolean isCompilable() {
115
132
116
133
@ Override
117
134
public void generateCode (MethodVisitor mv , CodeFlow cf ) {
118
- if (this . name . equals (ROOT )) {
119
- mv .visitVarInsn (ALOAD ,1 );
135
+ if (ROOT . equals (this . name )) {
136
+ mv .visitVarInsn (ALOAD , 1 );
120
137
}
121
138
else {
122
139
mv .visitVarInsn (ALOAD , 2 );
123
140
mv .visitLdcInsn (this .name );
124
- mv .visitMethodInsn (INVOKEINTERFACE , "org/springframework/expression/EvaluationContext" , "lookupVariable" , "(Ljava/lang/String;)Ljava/lang/Object;" ,true );
141
+ mv .visitMethodInsn (INVOKEINTERFACE , "org/springframework/expression/EvaluationContext" ,
142
+ "lookupVariable" , "(Ljava/lang/String;)Ljava/lang/Object;" , true );
125
143
}
126
144
CodeFlow .insertCheckCast (mv , this .exitTypeDescriptor );
127
145
cf .pushDescriptor (this .exitTypeDescriptor );
0 commit comments