From aaf33100d9fdfa11917f76048a71ca21f3eace3b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:17:44 +0300 Subject: [PATCH] Add IndexAccessor support to SpEL's SimpleEvaluationContext Closes gh-32702 --- .../spel/support/SimpleEvaluationContext.java | 41 ++++++++++++++++--- .../expression/spel/IndexingTests.java | 26 ++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java index 8e0147248f..f5836244f8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java @@ -27,6 +27,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.BeanResolver; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.IndexAccessor; import org.springframework.expression.MethodResolver; import org.springframework.expression.OperatorOverloader; import org.springframework.expression.PropertyAccessor; @@ -107,6 +108,8 @@ public final class SimpleEvaluationContext implements EvaluationContext { private final List propertyAccessors; + private final List indexAccessors; + private final List methodResolvers; private final TypeConverter typeConverter; @@ -118,10 +121,11 @@ public final class SimpleEvaluationContext implements EvaluationContext { private final Map variables = new HashMap<>(); - private SimpleEvaluationContext(List accessors, List resolvers, - @Nullable TypeConverter converter, @Nullable TypedValue rootObject) { + private SimpleEvaluationContext(List propertyAccessors, List indexAccessors, + List resolvers, @Nullable TypeConverter converter, @Nullable TypedValue rootObject) { - this.propertyAccessors = accessors; + this.propertyAccessors = propertyAccessors; + this.indexAccessors = indexAccessors; this.methodResolvers = resolvers; this.typeConverter = (converter != null ? converter : new StandardTypeConverter()); this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL); @@ -145,6 +149,16 @@ public final class SimpleEvaluationContext implements EvaluationContext { return this.propertyAccessors; } + /** + * Return the specified {@link IndexAccessor} delegates, if any. + * @since 6.2 + * @see Builder#withIndexAccessors(IndexAccessor...) + */ + @Override + public List getIndexAccessors() { + return this.indexAccessors; + } + /** * Return the specified {@link MethodResolver} delegates, if any. * @see Builder#withMethodResolvers @@ -289,7 +303,9 @@ public final class SimpleEvaluationContext implements EvaluationContext { */ public static final class Builder { - private final List accessors; + private final List propertyAccessors; + + private List indexAccessors = Collections.emptyList(); private List resolvers = Collections.emptyList(); @@ -299,8 +315,20 @@ public final class SimpleEvaluationContext implements EvaluationContext { @Nullable private TypedValue rootObject; + private Builder(PropertyAccessor... accessors) { - this.accessors = Arrays.asList(accessors); + this.propertyAccessors = Arrays.asList(accessors); + } + + + /** + * Register the specified {@link IndexAccessor} delegates. + * @param indexAccessors the index accessors to use + * @since 6.2 + */ + public Builder withIndexAccessors(IndexAccessor... indexAccessors) { + this.indexAccessors = Arrays.asList(indexAccessors); + return this; } /** @@ -381,7 +409,8 @@ public final class SimpleEvaluationContext implements EvaluationContext { } public SimpleEvaluationContext build() { - return new SimpleEvaluationContext(this.accessors, this.resolvers, this.typeConverter, this.rootObject); + return new SimpleEvaluationContext(this.propertyAccessors, this.indexAccessors, + this.resolvers, this.typeConverter, this.rootObject); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java index bcc4ee2a45..42ff88f34c 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java @@ -43,6 +43,7 @@ import org.springframework.expression.IndexAccessor; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.testresources.Person; import org.springframework.lang.Nullable; @@ -696,6 +697,31 @@ class IndexingTests { assertThat(expr.getValue(context, arrayNode)).isNull(); } + @Test + void readAndWriteIndexWithSimpleEvaluationContext() { + ObjectMapper objectMapper = new ObjectMapper(); + SimpleEvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding() + .withIndexAccessors(new JacksonArrayNodeIndexAccessor(objectMapper)) + .build(); + SpelExpressionParser parser = new SpelExpressionParser(); + + TextNode node0 = new TextNode("node0"); + TextNode node1 = new TextNode("node1"); + ArrayNode arrayNode = objectMapper.createArrayNode(); + arrayNode.addAll(List.of(node0, node1)); + + Expression expr = parser.parseExpression("[0]"); + assertThat(expr.getValue(context, arrayNode)).isSameAs(node0); + + TextNode nodeX = new TextNode("nodeX"); + expr.setValue(context, arrayNode, nodeX); + // We use isEqualTo() instead of isSameAs(), since ObjectMapper.convertValue() + // converts the supplied TextNode to an equivalent JsonNode. + assertThat(expr.getValue(context, arrayNode)).isEqualTo(nodeX); + + expr = parser.parseExpression("[1]"); + assertThat(expr.getValue(context, arrayNode)).isSameAs(node1); + } /** * {@link IndexAccessor} that knows how to read and write indexes in a