Skip to content

Commit 051adf2

Browse files
committed
Introduce MapAccessor in SpEL and deprecate existing implementation
Prior to this commit, the MapAccessor for SpEL resided in the org.springframework.context.expression package in the spring-context module; however, it arguably should reside in the spring-expression module so that it can be used whenever SpEL is used (without requiring spring-context). This commit therefore officially deprecates the MapAccessor in spring-context for removal in favor of a new copy of the implementation in the org.springframework.expression.spel.support package in the spring-expression module. Closes gh-35537
1 parent 27b2243 commit 051adf2

File tree

8 files changed

+210
-121
lines changed

8 files changed

+210
-121
lines changed

spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java

Lines changed: 4 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,7 @@
1616

1717
package org.springframework.context.expression;
1818

19-
import java.util.Map;
20-
21-
import org.jspecify.annotations.Nullable;
22-
23-
import org.springframework.asm.MethodVisitor;
24-
import org.springframework.expression.AccessException;
25-
import org.springframework.expression.EvaluationContext;
2619
import org.springframework.expression.PropertyAccessor;
27-
import org.springframework.expression.TypedValue;
28-
import org.springframework.expression.spel.CodeFlow;
29-
import org.springframework.expression.spel.CompilablePropertyAccessor;
30-
import org.springframework.util.Assert;
3120

3221
/**
3322
* SpEL {@link PropertyAccessor} that knows how to access the keys of a standard
@@ -36,11 +25,10 @@
3625
* @author Juergen Hoeller
3726
* @author Andy Clement
3827
* @since 3.0
28+
* @deprecated as of Spring Framework 7.0 in favor of {@link org.springframework.expression.spel.support.MapAccessor}.
3929
*/
40-
public class MapAccessor implements CompilablePropertyAccessor {
41-
42-
private final boolean allowWrite;
43-
30+
@Deprecated(since = "7.0", forRemoval = true)
31+
public class MapAccessor extends org.springframework.expression.spel.support.MapAccessor {
4432

4533
/**
4634
* Create a new {@code MapAccessor} for reading as well as writing.
@@ -57,88 +45,7 @@ public MapAccessor() {
5745
* @see #canWrite
5846
*/
5947
public MapAccessor(boolean allowWrite) {
60-
this.allowWrite = allowWrite;
61-
}
62-
63-
64-
@Override
65-
public Class<?>[] getSpecificTargetClasses() {
66-
return new Class<?>[] {Map.class};
67-
}
68-
69-
@Override
70-
public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
71-
return (target instanceof Map<?, ?> map && map.containsKey(name));
72-
}
73-
74-
@Override
75-
public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
76-
Assert.state(target instanceof Map, "Target must be of type Map");
77-
Map<?, ?> map = (Map<?, ?>) target;
78-
Object value = map.get(name);
79-
if (value == null && !map.containsKey(name)) {
80-
throw new MapAccessException(name);
81-
}
82-
return new TypedValue(value);
83-
}
84-
85-
@Override
86-
public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
87-
return (this.allowWrite && target instanceof Map);
88-
}
89-
90-
@Override
91-
@SuppressWarnings("unchecked")
92-
public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue)
93-
throws AccessException {
94-
95-
Assert.state(target instanceof Map, "Target must be of type Map");
96-
Map<Object, Object> map = (Map<Object, Object>) target;
97-
map.put(name, newValue);
98-
}
99-
100-
@Override
101-
public boolean isCompilable() {
102-
return true;
103-
}
104-
105-
@Override
106-
public Class<?> getPropertyType() {
107-
return Object.class;
108-
}
109-
110-
@Override
111-
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
112-
String descriptor = cf.lastDescriptor();
113-
if (descriptor == null || !descriptor.equals("Ljava/util/Map")) {
114-
if (descriptor == null) {
115-
cf.loadTarget(mv);
116-
}
117-
CodeFlow.insertCheckCast(mv, "Ljava/util/Map");
118-
}
119-
mv.visitLdcInsn(propertyName);
120-
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
121-
}
122-
123-
124-
/**
125-
* Exception thrown from {@code read} in order to reset a cached
126-
* PropertyAccessor, allowing other accessors to have a try.
127-
*/
128-
@SuppressWarnings("serial")
129-
private static class MapAccessException extends AccessException {
130-
131-
private final String key;
132-
133-
public MapAccessException(String key) {
134-
super("");
135-
this.key = key;
136-
}
137-
138-
@Override
139-
public String getMessage() {
140-
return "Map does not contain a value for key '" + this.key + "'";
141-
}
48+
super(allowWrite);
14249
}
14350

14451
}

spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void setExpressionParser(ExpressionParser expressionParser) {
168168
StandardEvaluationContext sec = new StandardEvaluationContext(bec);
169169
sec.addPropertyAccessor(new BeanExpressionContextAccessor());
170170
sec.addPropertyAccessor(new BeanFactoryAccessor());
171-
sec.addPropertyAccessor(new MapAccessor());
171+
sec.addPropertyAccessor(new org.springframework.expression.spel.support.MapAccessor());
172172
sec.addPropertyAccessor(new EnvironmentAccessor());
173173
sec.setBeanResolver(new BeanFactoryResolver(beanFactory));
174174
sec.setTypeLocator(new StandardTypeLocator(beanFactory.getBeanClassLoader()));

spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* @author Andy Clement
3737
* @author Sam Brannen
3838
*/
39+
@SuppressWarnings("removal")
3940
class MapAccessorTests {
4041

4142
private final StandardEvaluationContext context = new StandardEvaluationContext();
Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.expression.spel;
17+
package org.springframework.expression.spel.support;
1818

1919
import java.util.Map;
2020

@@ -23,37 +23,39 @@
2323
import org.springframework.asm.MethodVisitor;
2424
import org.springframework.expression.AccessException;
2525
import org.springframework.expression.EvaluationContext;
26+
import org.springframework.expression.PropertyAccessor;
2627
import org.springframework.expression.TypedValue;
28+
import org.springframework.expression.spel.CodeFlow;
29+
import org.springframework.expression.spel.CompilablePropertyAccessor;
2730
import org.springframework.util.Assert;
2831

2932
/**
30-
* This is a local COPY of {@link org.springframework.context.expression.MapAccessor}.
33+
* SpEL {@link PropertyAccessor} that knows how to access the keys of a standard
34+
* {@link java.util.Map}.
3135
*
3236
* @author Juergen Hoeller
3337
* @author Andy Clement
34-
* @since 4.1
38+
* @since 7.0
3539
*/
36-
public class CompilableMapAccessor implements CompilablePropertyAccessor {
40+
public class MapAccessor implements CompilablePropertyAccessor {
3741

3842
private final boolean allowWrite;
3943

4044

4145
/**
42-
* Create a new {@code CompilableMapAccessor} for reading as well as writing.
43-
* @since 6.2
44-
* @see #CompilableMapAccessor(boolean)
46+
* Create a new {@code MapAccessor} for reading as well as writing.
47+
* @see #MapAccessor(boolean)
4548
*/
46-
public CompilableMapAccessor() {
49+
public MapAccessor() {
4750
this(true);
4851
}
4952

5053
/**
51-
* Create a new {@code CompilableMapAccessor} for reading and possibly also writing.
54+
* Create a new {@code MapAccessor} for reading and possibly also writing.
5255
* @param allowWrite whether to allow write operations on a target instance
53-
* @since 6.2
5456
* @see #canWrite
5557
*/
56-
public CompilableMapAccessor(boolean allowWrite) {
58+
public MapAccessor(boolean allowWrite) {
5759
this.allowWrite = allowWrite;
5860
}
5961

spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springframework.expression.spel.standard.SpelCompiler;
5858
import org.springframework.expression.spel.standard.SpelExpression;
5959
import org.springframework.expression.spel.standard.SpelExpressionParser;
60+
import org.springframework.expression.spel.support.MapAccessor;
6061
import org.springframework.expression.spel.support.ReflectiveIndexAccessor;
6162
import org.springframework.expression.spel.support.StandardEvaluationContext;
6263
import org.springframework.expression.spel.testdata.PersonInOtherPackage;
@@ -618,9 +619,9 @@ void indexIntoMapOfPrimitiveIntArray() {
618619
}
619620

620621
@Test // gh-32356
621-
void indexIntoMapOfPrimitiveIntArrayWithCompilableMapAccessor() {
622+
void indexIntoMapOfPrimitiveIntArrayWithMapAccessor() {
622623
StandardEvaluationContext context = new StandardEvaluationContext();
623-
context.addPropertyAccessor(new CompilableMapAccessor());
624+
context.addPropertyAccessor(new MapAccessor());
624625

625626
Map<String, int[]> map = Map.of("foo", new int[] { 1, 2, 3 });
626627

@@ -635,21 +636,21 @@ void indexIntoMapOfPrimitiveIntArrayWithCompilableMapAccessor() {
635636
assertThat(stringify(expression.getValue(context, map))).isEqualTo("1 2 3");
636637
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
637638

638-
// custom CompilableMapAccessor via implicit #root & array index
639+
// custom MapAccessor via implicit #root & array index
639640
expression = parser.parseExpression("foo[1]");
640641

641642
assertThat(expression.getValue(context, map)).isEqualTo(2);
642643
assertCanCompile(expression);
643644
assertThat(expression.getValue(context, map)).isEqualTo(2);
644645

645-
// custom CompilableMapAccessor via explicit #root & array index
646+
// custom MapAccessor via explicit #root & array index
646647
expression = parser.parseExpression("#root.foo[1]");
647648

648649
assertThat(expression.getValue(context, map)).isEqualTo(2);
649650
assertCanCompile(expression);
650651
assertThat(expression.getValue(context, map)).isEqualTo(2);
651652

652-
// custom CompilableMapAccessor via explicit #this & array index
653+
// custom MapAccessor via explicit #this & array index
653654
expression = parser.parseExpression("#this.foo[1]");
654655

655656
assertThat(expression.getValue(context, map)).isEqualTo(2);

0 commit comments

Comments
 (0)