Skip to content

Commit d912770

Browse files
committed
Initial review and polish of IndexAccessor support in SpEL
See gh-26409 See gh-26478
1 parent 50a0000 commit d912770

File tree

11 files changed

+405
-262
lines changed

11 files changed

+405
-262
lines changed

spring-expression/spring-expression.gradle

-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@ dependencies {
99
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
1010
testImplementation("org.jetbrains.kotlin:kotlin-stdlib")
1111
testImplementation("com.fasterxml.jackson.core:jackson-databind")
12-
testImplementation("com.fasterxml.jackson.core:jackson-core")
1312
}

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.expression;
1818

19+
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.function.Supplier;
2122

@@ -57,9 +58,14 @@ public interface EvaluationContext {
5758
List<PropertyAccessor> getPropertyAccessors();
5859

5960
/**
60-
* Return a list of index accessors that will be asked in turn to read/write a property.
61+
* Return a list of index accessors that will be asked in turn to access or
62+
* set an indexed value.
63+
* <p>The default implementation returns an empty list.
64+
* @since 6.2
6165
*/
62-
List<IndexAccessor> getIndexAccessors();
66+
default List<IndexAccessor> getIndexAccessors() {
67+
return Collections.emptyList();
68+
}
6369

6470
/**
6571
* Return a list of resolvers that will be asked in turn to locate a constructor.
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -20,78 +20,91 @@
2020
import org.springframework.lang.Nullable;
2121

2222
/**
23-
* A index accessor is able to read from (and possibly write to) an array's elements.
23+
* An index accessor is able to read from (and possibly write to) an indexed
24+
* structure of an object.
2425
*
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.
26+
* <p>This interface places no restrictions on what constitutes an indexed
27+
* structure. Implementors are therefore free to access indexed values any way
28+
* they deem appropriate.
2729
*
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.
30+
* <p>An index accessor can optionally specify an array of target classes for
31+
* which it should be called. However, if it returns {@code null} or an empty
32+
* array from {@link #getSpecificTargetClasses()}, it will be called for all
33+
* indexing operations and given a chance to determine if it can read from or
34+
* write to the indexed structure.
3235
*
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.
36+
* <p>Index accessors are considered to be ordered, and each will be called in
37+
* turn. The only rule that affects the call order is that any index accessor
38+
* which specifies explicit support for the target class via
39+
* {@link #getSpecificTargetClasses()} will be called first, before other
40+
* generic index accessors.
3741
*
38-
* @author jackmiking lee
39-
* @since 3.0
42+
* @author Jackmiking Lee
43+
* @author Sam Brannen
44+
* @since 6.2
45+
* @see PropertyAccessor
4046
*/
41-
public interface IndexAccessor {
47+
public interface IndexAccessor extends TargetedAccessor {
48+
4249
/**
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)
50+
* Get the set of classes for which this index accessor should be called.
51+
* <p>Returning {@code null} or an empty array indicates this is a generic
52+
* index accessor that can be called in an attempt to access an index on any
53+
* type.
54+
* @return an array of classes that this index accessor is suitable for
55+
* (or {@code null} or an empty array if a generic index accessor)
4856
*/
57+
@Override
4958
@Nullable
5059
Class<?>[] getSpecificTargetClasses();
5160

5261
/**
53-
* Called to determine if a resolver instance is able to access a specified property
54-
* on a specified target object.
62+
* Called to determine if this index accessor is able to read a specified
63+
* index on a specified target object.
5564
* @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
65+
* @param target the target object upon which the index is being accessed
66+
* @param index the index being accessed
67+
* @return true if this index accessor is able to read the index
68+
* @throws AccessException if there is any problem determining whether the
69+
* index can be read
6070
*/
6171
boolean canRead(EvaluationContext context, @Nullable Object target, Object index) throws AccessException;
6272

6373
/**
64-
* Called to read a property from a specified target object.
65-
* Should only succeed if {@link #canRead} also returns {@code true}.
74+
* Called to read an index from a specified target object.
75+
* <p>Should only succeed if {@link #canRead} also returns {@code true}.
6676
* @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
77+
* @param target the target object upon which the index is being accessed
78+
* @param index the index being accessed
79+
* @return a TypedValue object wrapping the index value read and a type
80+
* descriptor for it
81+
* @throws AccessException if there is any problem reading the index value
7182
*/
72-
ValueRef read(EvaluationContext context, @Nullable Object target,Object index) throws AccessException;
83+
// TODO Change return type to TypedValue to avoid package cycle.
84+
ValueRef read(EvaluationContext context, @Nullable Object target, Object index) throws AccessException;
7385

7486
/**
75-
* Called to determine if a resolver instance is able to write to a specified
76-
* property on a specified target object.
87+
* Called to determine if this index accessor is able to write to a specified
88+
* index on a specified target object.
7789
* @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
90+
* @param target the target object upon which the index is being accessed
91+
* @param index the index being accessed
92+
* @return true if this index accessor is able to write to the index
8193
* @throws AccessException if there is any problem determining whether the
82-
* property can be written to
94+
* index can be written to
8395
*/
8496
boolean canWrite(EvaluationContext context, @Nullable Object target, Object index) throws AccessException;
8597

8698
/**
87-
* Called to write to a property on a specified target object.
88-
* Should only succeed if {@link #canWrite} also returns {@code true}.
99+
* Called to write to an index on a specified target object.
100+
* <p>Should only succeed if {@link #canWrite} also returns {@code true}.
89101
* @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
102+
* @param target the target object upon which the index is being accessed
103+
* @param index the index being accessed
104+
* @param newValue the new value for the index
105+
* @throws AccessException if there is any problem writing to the index value
94106
*/
95107
void write(EvaluationContext context, @Nullable Object target, Object index, @Nullable Object newValue)
96108
throws AccessException;
109+
97110
}

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

+9-6
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,24 @@
3434
* <p>Property accessors are considered to be ordered, and each will be called in
3535
* turn. The only rule that affects the call order is that any property accessor
3636
* which specifies explicit support for the target class via
37-
* {@link #getSpecificTargetClasses()} will be called first, before the general
37+
* {@link #getSpecificTargetClasses()} will be called first, before the generic
3838
* property accessors.
3939
*
4040
* @author Andy Clement
4141
* @since 3.0
42+
* @see IndexAccessor
4243
*/
43-
public interface PropertyAccessor {
44+
public interface PropertyAccessor extends TargetedAccessor {
4445

4546
/**
46-
* Return an array of classes for which this property accessor should be called.
47-
* <p>Returning {@code null} indicates this is a general property accessor that
48-
* can be called in an attempt to access a property on any type.
47+
* Get the set of classes for which this property accessor should be called.
48+
* <p>Returning {@code null} or an empty array indicates this is a generic
49+
* property accessor that can be called in an attempt to access a property on
50+
* any type.
4951
* @return an array of classes that this property accessor is suitable for
50-
* (or {@code null} if a general property accessor)
52+
* (or {@code null} if a generic property accessor)
5153
*/
54+
@Override
5255
@Nullable
5356
Class<?>[] getSpecificTargetClasses();
5457

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2002-2024 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.lang.Nullable;
20+
21+
/**
22+
* Strategy for types that access elements of specific target classes.
23+
*
24+
* <p>This interface places no restrictions on what constitutes an element.
25+
*
26+
* <p>A targeted accessor can specify a set of target classes for which it should
27+
* be called. However, if it returns {@code null} or an empty array from
28+
* {@link #getSpecificTargetClasses()}, it will typically be called for all
29+
* access operations and given a chance to determine if it supports a concrete
30+
* access attempt.
31+
*
32+
* <p>Targeted accessors are considered to be ordered, and each will be called
33+
* in turn. The only rule that affects the call order is that any accessor which
34+
* specifies explicit support for a given target class via
35+
* {@link #getSpecificTargetClasses()} will be called first, before other generic
36+
* accessors that do not specify explicit support for the given target class.
37+
*
38+
* @author Sam Brannen
39+
* @since 6.2
40+
* @see PropertyAccessor
41+
* @see IndexAccessor
42+
*/
43+
public interface TargetedAccessor {
44+
45+
/**
46+
* Get the set of classes for which this accessor should be called.
47+
* <p>Returning {@code null} or an empty array indicates this is a generic
48+
* accessor that can be called in an attempt to access an element on any
49+
* type.
50+
* @return an array of classes that this accessor is suitable for
51+
* (or {@code null} or an empty array if a generic accessor)
52+
*/
53+
@Nullable
54+
Class<?>[] getSpecificTargetClasses();
55+
56+
}

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

+61-20
Original file line numberDiff line numberDiff line change
@@ -17,63 +17,104 @@
1717
package org.springframework.expression.spel.ast;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.List;
2122

2223
import org.springframework.expression.PropertyAccessor;
24+
import org.springframework.expression.TargetedAccessor;
2325
import org.springframework.lang.Nullable;
2426
import org.springframework.util.ObjectUtils;
2527

2628
/**
2729
* Utility methods for use in the AST classes.
2830
*
2931
* @author Andy Clement
32+
* @author Sam Brannen
3033
* @since 3.0.2
3134
*/
3235
public abstract class AstUtils {
3336

3437
/**
35-
* Determine the set of property accessors that should be used to try to
36-
* access a property on the specified target type.
38+
* Determine the set of accessors that should be used to try to access an
39+
* element on the specified target type.
3740
* <p>The accessors are considered to be in an ordered list; however, in the
3841
* returned list any accessors that are exact matches for the input target
39-
* type (as opposed to 'general' accessors that could work for any type) are
42+
* type (as opposed to 'generic' accessors that could work for any type) are
4043
* placed at the start of the list. In addition, if there are specific
4144
* accessors that exactly name the class in question and accessors that name
4245
* a specific class which is a supertype of the class in question, the latter
4346
* are put at the end of the specific accessors set and will be tried after
4447
* exactly matching accessors but before generic accessors.
45-
* @param targetType the type upon which property access is being attempted
46-
* @param propertyAccessors the list of property accessors to process
47-
* @return a list of accessors that should be tried in order to access the property
48+
* @param targetType the type upon which element access is being attempted
49+
* @param accessors the list of element accessors to process
50+
* @return a list of accessors that should be tried in order to access the
51+
* element on the specified target type, or an empty list if no suitable
52+
* accessor could be found
53+
* @since 6.2
4854
*/
49-
public static List<PropertyAccessor> getPropertyAccessorsToTry(
50-
@Nullable Class<?> targetType, List<PropertyAccessor> propertyAccessors) {
55+
public static <T extends TargetedAccessor> List<T> getAccessorsToTry(
56+
@Nullable Class<?> targetType, List<T> accessors) {
57+
58+
if (accessors.isEmpty()) {
59+
return Collections.emptyList();
60+
}
5161

52-
List<PropertyAccessor> specificAccessors = new ArrayList<>();
53-
List<PropertyAccessor> generalAccessors = new ArrayList<>();
54-
for (PropertyAccessor accessor : propertyAccessors) {
62+
List<T> exactMatches = new ArrayList<>();
63+
List<T> inexactMatches = new ArrayList<>();
64+
List<T> genericMatches = new ArrayList<>();
65+
for (T accessor : accessors) {
5566
Class<?>[] targets = accessor.getSpecificTargetClasses();
5667
if (ObjectUtils.isEmpty(targets)) {
5768
// generic accessor that says it can be used for any type
58-
generalAccessors.add(accessor);
69+
genericMatches.add(accessor);
5970
}
6071
else if (targetType != null) {
6172
for (Class<?> clazz : targets) {
6273
if (clazz == targetType) {
63-
// add exact matches to the specificAccessors list
64-
specificAccessors.add(accessor);
74+
exactMatches.add(accessor);
6575
}
6676
else if (clazz.isAssignableFrom(targetType)) {
67-
// add supertype matches to the front of the generalAccessors list
68-
generalAccessors.add(0, accessor);
77+
inexactMatches.add(accessor);
6978
}
7079
}
7180
}
7281
}
73-
List<PropertyAccessor> accessors = new ArrayList<>(specificAccessors.size() + generalAccessors.size());
74-
accessors.addAll(specificAccessors);
75-
accessors.addAll(generalAccessors);
76-
return accessors;
82+
83+
int size = exactMatches.size() + inexactMatches.size() + genericMatches.size();
84+
if (size == 0) {
85+
return Collections.emptyList();
86+
}
87+
else {
88+
List<T> result = new ArrayList<>(size);
89+
result.addAll(exactMatches);
90+
result.addAll(inexactMatches);
91+
result.addAll(genericMatches);
92+
return result;
93+
}
94+
}
95+
96+
/**
97+
* Determine the set of property accessors that should be used to try to
98+
* access a property on the specified target type.
99+
* <p>The accessors are considered to be in an ordered list; however, in the
100+
* returned list any accessors that are exact matches for the input target
101+
* type (as opposed to 'generic' accessors that could work for any type) are
102+
* placed at the start of the list. In addition, if there are specific
103+
* accessors that exactly name the class in question and accessors that name
104+
* a specific class which is a supertype of the class in question, the latter
105+
* are put at the end of the specific accessors set and will be tried after
106+
* exactly matching accessors but before generic accessors.
107+
* @param targetType the type upon which property access is being attempted
108+
* @param propertyAccessors the list of property accessors to process
109+
* @return a list of accessors that should be tried in order to access the
110+
* property on the specified target type, or an empty list if no suitable
111+
* accessor could be found
112+
* @see #getAccessorsToTry(Class, List)
113+
*/
114+
public static List<PropertyAccessor> getPropertyAccessorsToTry(
115+
@Nullable Class<?> targetType, List<PropertyAccessor> propertyAccessors) {
116+
117+
return getAccessorsToTry(targetType, propertyAccessors);
77118
}
78119

79120
}

0 commit comments

Comments
 (0)