FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

SpEL: ensure correct object used for nested #this references · Java1024/spring-framework@91ed5b6 · GitHub

Repository navigation

Commit 91ed5b6

Browse files
committed
SpEL: ensure correct object used for nested #this references
Before this commit the object that #this would refer to in nested expressions within projection/selection clauses was always the root context object. This was incorrect as it should be the element being projected/selected over. This commit introduces a scope root context object which is set upon entering a new scope (like when entering a projection or selection). Any object. With this change this kind of expression now behaves: where #this is the element of list1. Unqualified references are also resolved against this scope root context object. Issues: SPR-10417, SPR-12035, SPR-13055
1 parent 5a3eea8 commit 91ed5b6

5 files changed

Lines changed: 197 additions & 7 deletions

File tree

‎spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.expression.spel;
1818

19+
import java.util.Collections;
1920
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
@@ -52,6 +53,15 @@ public class ExpressionState {
5253

5354
private final TypedValue rootObject;
5455

56+
// When entering a new scope there is a new base object which should be used
57+
// for '#this' references (or to act as a target for unqualified references).
58+
// This stack captures those objects at each nested scope level.
59+
// For example:
60+
// #list1.?[#list2.contains(#this)]
61+
// On entering the selection we enter a new scope, and #this is now the
62+
// element from list1
63+
private Stack<TypedValue> scopeRootObjects;
64+
5565
private final SpelParserConfiguration configuration;
5666

5767
private Stack<VariableScope> variableScopes;
@@ -86,6 +96,9 @@ private void ensureVariableScopesInitialized() {
8696
// top level empty variable scope
8797
this.variableScopes.add(new VariableScope());
8898
}
99+
if (this.scopeRootObjects == null) {
100+
this.scopeRootObjects = new Stack<TypedValue>();
101+
}
89102
}
90103

91104
/**
@@ -116,6 +129,13 @@ public TypedValue getRootContextObject() {
116129
return this.rootObject;
117130
}
118131

132+
public TypedValue getScopeRootContextObject() {
133+
if (this.scopeRootObjects == null || this.scopeRootObjects.isEmpty()) {
134+
return this.rootObject;
135+
}
136+
return this.scopeRootObjects.peek();
137+
}
138+
119139
public void setVariable(String name, Object value) {
120140
this.relatedContext.setVariable(name, value);
121141
}
@@ -158,16 +178,25 @@ public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor
158178
public void enterScope(Map<String, Object> argMap) {
159179
ensureVariableScopesInitialized();
160180
this.variableScopes.push(new VariableScope(argMap));
181+
this.scopeRootObjects.push(getActiveContextObject());
182+
}
183+
184+
public void enterScope() {
185+
ensureVariableScopesInitialized();
186+
this.variableScopes.push(new VariableScope(Collections.<String,Object>emptyMap()));
187+
this.scopeRootObjects.push(getActiveContextObject());
161188
}
162189

163190
public void enterScope(String name, Object value) {
164191
ensureVariableScopesInitialized();
165192
this.variableScopes.push(new VariableScope(name, value));
193+
this.scopeRootObjects.push(getActiveContextObject());
166194
}
167195

168196
public void exitScope() {
169197
ensureVariableScopesInitialized();
170198
this.variableScopes.pop();
199+
this.scopeRootObjects.pop();
171200
}
172201

173202
public void setLocalVariable(String name, Object value) {

‎spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -150,7 +150,7 @@ private Object[] getArguments(ExpressionState state) {
150150
for (int i = 0; i < arguments.length; i++) {
151151
// Make the root object the active context again for evaluating the parameter expressions
152152
try {
153-
state.pushActiveContextObject(state.getRootContextObject());
153+
state.pushActiveContextObject(state.getScopeRootContextObject());
154154
arguments[i] = this.children[i].getValueInternal(state).getValue();
155155
}
156156
finally {

‎spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -68,17 +68,19 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
6868
// before calling the specified operation. This special context object
6969
// has two fields 'key' and 'value' that refer to the map entries key
7070
// and value, and they can be referenced in the operation
71-
// eg. {'a':'y','b':'n'}.!{value=='y'?key:null}" == ['a', null]
71+
// eg. {'a':'y','b':'n'}.![value=='y'?key:null]" == ['a', null]
7272
if (operand instanceof Map) {
7373
Map<?, ?> mapData = (Map<?, ?>) operand;
7474
List<Object> result = new ArrayList<Object>();
7575
for (Map.Entry<?, ?> entry : mapData.entrySet()) {
7676
try {
7777
state.pushActiveContextObject(new TypedValue(entry));
78+
state.enterScope();
7879
result.add(this.children[0].getValueInternal(state).getValue());
7980
}
8081
finally {
8182
state.popActiveContextObject();
83+
state.exitScope();
8284
}
8385
}
8486
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); // TODO unable to build correct type descriptor

‎spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,6 +86,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
8686
try {
8787
TypedValue kvPair = new TypedValue(entry);
8888
state.pushActiveContextObject(kvPair);
89+
state.enterScope();
8990
Object val = selectionCriteria.getValueInternal(state).getValue();
9091
if (val instanceof Boolean) {
9192
if ((Boolean) val) {
@@ -104,6 +105,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
104105
}
105106
finally {
106107
state.popActiveContextObject();
108+
state.exitScope();
107109
}
108110
}
109111
if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) {

‎spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java‎

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.junit.Rule;
3737
import org.junit.Test;
3838
import org.junit.rules.ExpectedException;
39-
4039
import org.springframework.core.MethodParameter;
4140
import org.springframework.core.convert.TypeDescriptor;
4241
import org.springframework.expression.AccessException;
@@ -1918,6 +1917,164 @@ public void SPR12808() throws Exception {
19181917
sec.setVariable("no", "1.0");
19191918
assertTrue(expression.getValue(sec).toString().startsWith("Object"));
19201919
}
1920+
1921+
@Test
1922+
@SuppressWarnings("rawtypes")
1923+
public void SPR13055() throws Exception {
1924+
List<Map<String, Object>> myPayload = new ArrayList<Map<String, Object>>();
1925+
1926+
Map<String, Object> v1 = new HashMap<String, Object>();
1927+
Map<String, Object> v2 = new HashMap<String, Object>();
1928+
1929+
v1.put("test11", "test11");
1930+
v1.put("test12", "test12");
1931+
v2.put("test21", "test21");
1932+
v2.put("test22", "test22");
1933+
1934+
myPayload.add(v1);
1935+
myPayload.add(v2);
1936+
1937+
EvaluationContext context = new StandardEvaluationContext(myPayload);
1938+
1939+
ExpressionParser parser = new SpelExpressionParser();
1940+
1941+
String ex = "#root.![T(org.springframework.util.StringUtils).collectionToCommaDelimitedString(#this.values())]";
1942+
List res = parser.parseExpression(ex).getValue(context, List.class);
1943+
assertEquals("[test12,test11, test22,test21]", res.toString());
1944+
1945+
res = parser.parseExpression("#root.![#this.values()]").getValue(context,
1946+
List.class);
1947+
assertEquals("[[test12, test11], [test22, test21]]", res.toString());
1948+
1949+
res = parser.parseExpression("#root.![values()]").getValue(context, List.class);
1950+
assertEquals("[[test12, test11], [test22, test21]]", res.toString());
1951+
}
1952+
1953+
@Test
1954+
public void SPR12035() {
1955+
ExpressionParser parser = new SpelExpressionParser();
1956+
1957+
Expression expression1 = parser.parseExpression("list.?[ value>2 ].size()!=0");
1958+
assertTrue(expression1.getValue(new BeanClass(new ListOf(1.1), new ListOf(2.2)),
1959+
Boolean.class));
1960+
1961+
Expression expression2 = parser.parseExpression("list.?[ T(java.lang.Math).abs(value) > 2 ].size()!=0");
1962+
assertTrue(expression2.getValue(new BeanClass(new ListOf(1.1), new ListOf(-2.2)),
1963+
Boolean.class));
1964+
}
1965+
1966+
static class CCC {
1967+
public boolean method(Object o) {
1968+
System.out.println(o);
1969+
return false;
1970+
}
1971+
}
1972+
1973+
@Test
1974+
public void SPR13055_maps() {
1975+
EvaluationContext context = new StandardEvaluationContext();
1976+
ExpressionParser parser = new SpelExpressionParser();
1977+
1978+
Expression ex = parser.parseExpression("{'a':'y','b':'n'}.![value=='y'?key:null]");
1979+
assertEquals("[a, null]", ex.getValue(context).toString());
1980+
1981+
ex = parser.parseExpression("{2:4,3:6}.![T(java.lang.Math).abs(#this.key) + 5]");
1982+
assertEquals("[7, 8]", ex.getValue(context).toString());
1983+
1984+
ex = parser.parseExpression("{2:4,3:6}.![T(java.lang.Math).abs(#this.value) + 5]");
1985+
assertEquals("[9, 11]", ex.getValue(context).toString());
1986+
}
1987+
1988+
@Test
1989+
@SuppressWarnings({ "unchecked", "rawtypes" })
1990+
public void SPR10417() {
1991+
List list1 = new ArrayList();
1992+
list1.add("a");
1993+
list1.add("b");
1994+
list1.add("x");
1995+
List list2 = new ArrayList();
1996+
list2.add("c");
1997+
list2.add("x");
1998+
EvaluationContext context = new StandardEvaluationContext();
1999+
context.setVariable("list1", list1);
2000+
context.setVariable("list2", list2);
2001+
2002+
// #this should be the element from list1
2003+
Expression ex = parser.parseExpression("#list1.?[#list2.contains(#this)]");
2004+
Object result = ex.getValue(context);
2005+
assertEquals("[x]", result.toString());
2006+
2007+
// toString() should be called on the element from list1
2008+
ex = parser.parseExpression("#list1.?[#list2.contains(toString())]");
2009+
result = ex.getValue(context);
2010+
assertEquals("[x]", result.toString());
2011+
2012+
List list3 = new ArrayList();
2013+
list3.add(1);
2014+
list3.add(2);
2015+
list3.add(3);
2016+
list3.add(4);
2017+
2018+
context = new StandardEvaluationContext();
2019+
context.setVariable("list3", list3);
2020+
ex = parser.parseExpression("#list3.?[#this > 2]");
2021+
result = ex.getValue(context);
2022+
assertEquals("[3, 4]", result.toString());
2023+
2024+
ex = parser.parseExpression("#list3.?[#this >= T(java.lang.Math).abs(T(java.lang.Math).abs(#this))]");
2025+
result = ex.getValue(context);
2026+
assertEquals("[1, 2, 3, 4]", result.toString());
2027+
}
2028+
2029+
@Test
2030+
@SuppressWarnings({ "unchecked", "rawtypes" })
2031+
public void SPR10417_maps() {
2032+
Map map1 = new HashMap();
2033+
map1.put("A", 65);
2034+
map1.put("B", 66);
2035+
map1.put("X", 66);
2036+
Map map2 = new HashMap();
2037+
map2.put("X", 66);
2038+
2039+
EvaluationContext context = new StandardEvaluationContext();
2040+
context.setVariable("map1", map1);
2041+
context.setVariable("map2", map2);
2042+
2043+
// #this should be the element from list1
2044+
Expression ex = parser.parseExpression("#map1.?[#map2.containsKey(#this.getKey())]");
2045+
Object result = ex.getValue(context);
2046+
assertEquals("{X=66}", result.toString());
2047+
2048+
ex = parser.parseExpression("#map1.?[#map2.containsKey(key)]");
2049+
result = ex.getValue(context);
2050+
assertEquals("{X=66}", result.toString());
2051+
}
2052+
2053+
public static class ListOf {
2054+
2055+
private final double value;
2056+
2057+
public ListOf(double v) {
2058+
this.value = v;
2059+
}
2060+
2061+
public double getValue() {
2062+
return value;
2063+
}
2064+
}
2065+
2066+
public static class BeanClass {
2067+
2068+
private final List<ListOf> list;
2069+
2070+
public BeanClass(ListOf... list) {
2071+
this.list = Arrays.asList(list);
2072+
}
2073+
2074+
public List<ListOf> getList() {
2075+
return list;
2076+
}
2077+
}
19212078

19222079

19232080
private static enum ABC { A, B, C }

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL