Skip to content

Commit 1409ba7

Browse files
author
ljmn3211
committed
1. add indexAccessor for SpEL.
closes gh-26409
1 parent 2d29fcd commit 1409ba7

File tree

7 files changed

+312
-1
lines changed

7 files changed

+312
-1
lines changed

spring-expression/spring-expression.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ dependencies {
77
testCompile(testFixtures(project(":spring-core")))
88
testCompile("org.jetbrains.kotlin:kotlin-reflect")
99
testCompile("org.jetbrains.kotlin:kotlin-stdlib")
10+
testCompile("com.fasterxml.jackson.core:jackson-databind")
11+
testCompile("com.fasterxml.jackson.core:jackson-core")
1012
}

spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public interface EvaluationContext {
4646
*/
4747
List<PropertyAccessor> getPropertyAccessors();
4848

49+
/**
50+
* Return a list of index accessors that will be asked in turn to read/write a property.
51+
*/
52+
List<IndexAccessor> getIndexAccessors();
53+
4954
/**
5055
* Return a list of resolvers that will be asked in turn to locate a constructor.
5156
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.expression;
18+
19+
import org.springframework.expression.spel.ast.ValueRef;
20+
import org.springframework.lang.Nullable;
21+
22+
/**
23+
* A index accessor is able to read from (and possibly write to) an array's elements.
24+
*
25+
* <p>This interface places no restrictions, and so implementors are free to access elements
26+
* directly as fields or through getters or in any other way they see as appropriate.
27+
*
28+
* <p>A resolver can optionally specify an array of target classes for which it should be
29+
* called. However, if it returns {@code null} from {@link #getSpecificTargetClasses()},
30+
* it will be called for all property references and given a chance to determine if it
31+
* can read or write them.
32+
*
33+
* <p>Property resolvers are considered to be ordered, and each will be called in turn.
34+
* The only rule that affects the call order is that any resolver naming the target
35+
* class directly in {@link #getSpecificTargetClasses()} will be called first, before
36+
* the general resolvers.
37+
*
38+
* @author jackmiking lee
39+
* @since 3.0
40+
*/
41+
public interface IndexAccessor {
42+
/**
43+
* Return an array of classes for which this resolver should be called.
44+
* <p>Returning {@code null} indicates this is a general resolver that
45+
* can be called in an attempt to resolve a property on any type.
46+
* @return an array of classes that this resolver is suitable for
47+
* (or {@code null} if a general resolver)
48+
*/
49+
@Nullable
50+
Class<?>[] getSpecificTargetClasses();
51+
52+
/**
53+
* Called to determine if a resolver instance is able to access a specified property
54+
* on a specified target object.
55+
* @param context the evaluation context in which the access is being attempted
56+
* @param target the target object upon which the property is being accessed
57+
* @param index the index of the array being accessed
58+
* @return true if this resolver is able to read the property
59+
* @throws AccessException if there is any problem determining whether the property can be read
60+
*/
61+
boolean canRead(EvaluationContext context, @Nullable Object target, Object index) throws AccessException;
62+
63+
/**
64+
* Called to read a property from a specified target object.
65+
* Should only succeed if {@link #canRead} also returns {@code true}.
66+
* @param context the evaluation context in which the access is being attempted
67+
* @param target the target object upon which the property is being accessed
68+
* @param index the index of the array being accessed
69+
* @return a TypedValue object wrapping the property value read and a type descriptor for it
70+
* @throws AccessException if there is any problem accessing the property value
71+
*/
72+
ValueRef read(EvaluationContext context, @Nullable Object target,Object index) throws AccessException;
73+
74+
/**
75+
* Called to determine if a resolver instance is able to write to a specified
76+
* property on a specified target object.
77+
* @param context the evaluation context in which the access is being attempted
78+
* @param target the target object upon which the property is being accessed
79+
* @param index the index of the array being accessed
80+
* @return true if this resolver is able to write to the property
81+
* @throws AccessException if there is any problem determining whether the
82+
* property can be written to
83+
*/
84+
boolean canWrite(EvaluationContext context, @Nullable Object target, Object index) throws AccessException;
85+
86+
/**
87+
* Called to write to a property on a specified target object.
88+
* Should only succeed if {@link #canWrite} also returns {@code true}.
89+
* @param context the evaluation context in which the access is being attempted
90+
* @param target the target object upon which the property is being accessed
91+
* @param index the index of the array being accessed
92+
* @param newValue the new value for the property
93+
* @throws AccessException if there is any problem writing to the property value
94+
*/
95+
void write(EvaluationContext context, @Nullable Object target, Object index, @Nullable Object newValue)
96+
throws AccessException;
97+
}

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

+66-1
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,19 @@
2121
import java.lang.reflect.Member;
2222
import java.lang.reflect.Method;
2323
import java.lang.reflect.Modifier;
24+
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.List;
2627
import java.util.Map;
28+
import java.util.Optional;
2729
import java.util.StringJoiner;
2830

2931
import org.springframework.asm.MethodVisitor;
3032
import org.springframework.core.convert.TypeDescriptor;
3133
import org.springframework.expression.AccessException;
3234
import org.springframework.expression.EvaluationContext;
3335
import org.springframework.expression.EvaluationException;
36+
import org.springframework.expression.IndexAccessor;
3437
import org.springframework.expression.PropertyAccessor;
3538
import org.springframework.expression.TypeConverter;
3639
import org.springframework.expression.TypedValue;
@@ -187,11 +190,73 @@ else if (target instanceof Collection) {
187190
return new PropertyIndexingValueRef(
188191
target, (String) index, state.getEvaluationContext(), targetDescriptor);
189192
}
190-
193+
Optional<ValueRef> optional = tryIndexAccessor(state, index);
194+
if (optional.isPresent()) {
195+
return optional.get();
196+
}
191197
throw new SpelEvaluationException(
192198
getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetDescriptor);
193199
}
194200

201+
private Optional<ValueRef> tryIndexAccessor(ExpressionState state, Object index) {
202+
EvaluationContext context = state.getEvaluationContext();
203+
Object target = state.getActiveContextObject().getValue();
204+
if (context != null) {
205+
List<IndexAccessor> list = context.getIndexAccessors();
206+
if (list != null) {
207+
List<IndexAccessor> availableAccessors = getIndexAccessorsToTry(state.getActiveContextObject(), list);
208+
try {
209+
for (IndexAccessor indexAccessor : availableAccessors) {
210+
if (indexAccessor.canRead(context, target, index)) {
211+
ValueRef valueRef = indexAccessor.read(context, target, index);
212+
return Optional.of(valueRef);
213+
}
214+
}
215+
}
216+
catch (Exception ex) {
217+
}
218+
}
219+
}
220+
return Optional.empty();
221+
}
222+
223+
private List<IndexAccessor> getIndexAccessorsToTry(
224+
@Nullable Object contextObject, List<IndexAccessor> propertyAccessors) {
225+
226+
Class<?> targetType;
227+
if (contextObject instanceof TypedValue) {
228+
targetType = ((TypedValue) contextObject).getTypeDescriptor().getObjectType();
229+
}
230+
else {
231+
targetType = (contextObject != null ? contextObject.getClass() : null);
232+
}
233+
234+
List<IndexAccessor> specificAccessors = new ArrayList<>();
235+
List<IndexAccessor> generalAccessors = new ArrayList<>();
236+
for (IndexAccessor resolver : propertyAccessors) {
237+
Class<?>[] targets = resolver.getSpecificTargetClasses();
238+
if (targets == null) {
239+
// generic resolver that says it can be used for any type
240+
generalAccessors.add(resolver);
241+
}
242+
else if (targetType != null) {
243+
for (Class<?> clazz : targets) {
244+
if (clazz == targetType) {
245+
specificAccessors.add(resolver);
246+
break;
247+
}
248+
else if (clazz.isAssignableFrom(targetType)) {
249+
generalAccessors.add(resolver);
250+
}
251+
}
252+
}
253+
}
254+
List<IndexAccessor> resolvers = new ArrayList<>(specificAccessors);
255+
generalAccessors.removeAll(specificAccessors);
256+
resolvers.addAll(generalAccessors);
257+
return resolvers;
258+
}
259+
195260
@Override
196261
public boolean isCompilable() {
197262
if (this.indexedType == IndexedType.ARRAY) {

spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.expression.BeanResolver;
2828
import org.springframework.expression.ConstructorResolver;
2929
import org.springframework.expression.EvaluationContext;
30+
import org.springframework.expression.IndexAccessor;
3031
import org.springframework.expression.MethodResolver;
3132
import org.springframework.expression.OperatorOverloader;
3233
import org.springframework.expression.PropertyAccessor;
@@ -135,6 +136,11 @@ public List<PropertyAccessor> getPropertyAccessors() {
135136
return this.propertyAccessors;
136137
}
137138

139+
@Override
140+
public List<IndexAccessor> getIndexAccessors() {
141+
return null;
142+
}
143+
138144
/**
139145
* Return an empty list, always, since this context does not support the
140146
* use of type references.

spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java

+30
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.expression.BeanResolver;
2727
import org.springframework.expression.ConstructorResolver;
2828
import org.springframework.expression.EvaluationContext;
29+
import org.springframework.expression.IndexAccessor;
2930
import org.springframework.expression.MethodFilter;
3031
import org.springframework.expression.MethodResolver;
3132
import org.springframework.expression.OperatorOverloader;
@@ -66,6 +67,9 @@ public class StandardEvaluationContext implements EvaluationContext {
6667
@Nullable
6768
private volatile List<PropertyAccessor> propertyAccessors;
6869

70+
@Nullable
71+
private volatile List<IndexAccessor> indexAccessors;
72+
6973
@Nullable
7074
private volatile List<ConstructorResolver> constructorResolvers;
7175

@@ -125,6 +129,10 @@ public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
125129
this.propertyAccessors = propertyAccessors;
126130
}
127131

132+
public void setIndexAccessors(List<IndexAccessor>indexAccessors){
133+
this.indexAccessors=indexAccessors;
134+
}
135+
128136
@Override
129137
public List<PropertyAccessor> getPropertyAccessors() {
130138
return initPropertyAccessors();
@@ -138,6 +146,14 @@ public boolean removePropertyAccessor(PropertyAccessor accessor) {
138146
return initPropertyAccessors().remove(accessor);
139147
}
140148

149+
public void addIndexAccessor(IndexAccessor accessor){
150+
initIndexAccessors().add(accessor);
151+
}
152+
153+
public boolean removeIndexAccessor(IndexAccessor indexAccessor){
154+
return initIndexAccessors().remove(indexAccessor);
155+
}
156+
141157
public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers) {
142158
this.constructorResolvers = constructorResolvers;
143159
}
@@ -287,6 +303,15 @@ private List<PropertyAccessor> initPropertyAccessors() {
287303
return accessors;
288304
}
289305

306+
private List<IndexAccessor>initIndexAccessors(){
307+
List<IndexAccessor> accessors = this.indexAccessors;
308+
if(accessors == null){
309+
accessors = new ArrayList<>(5);
310+
this.indexAccessors = accessors;
311+
}
312+
return accessors;
313+
}
314+
290315
private List<ConstructorResolver> initConstructorResolvers() {
291316
List<ConstructorResolver> resolvers = this.constructorResolvers;
292317
if (resolvers == null) {
@@ -312,4 +337,9 @@ private static <T> void addBeforeDefault(List<T> resolvers, T resolver) {
312337
resolvers.add(resolvers.size() - 1, resolver);
313338
}
314339

340+
@Override
341+
public List<IndexAccessor> getIndexAccessors() {
342+
return initIndexAccessors();
343+
}
344+
315345
}

0 commit comments

Comments
 (0)