diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AccessorUtils.java similarity index 97% rename from spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java rename to spring-expression/src/main/java/org/springframework/expression/spel/ast/AccessorUtils.java index 4f1dc1e2f06..fcb03ced3b8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AccessorUtils.java @@ -25,13 +25,13 @@ import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** - * Utility methods for use in the AST classes. + * Utility methods for use with property and index accessors. * * @author Andy Clement * @author Sam Brannen * @since 3.0.2 */ -abstract class AstUtils { +abstract class AccessorUtils { /** * Determine the set of accessors that should be used to try to access an diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index cab438f0478..94cc5d37016 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -253,7 +253,7 @@ public class Indexer extends SpelNodeImpl { // Check for a custom IndexAccessor. EvaluationContext evalContext = state.getEvaluationContext(); List accessorsToTry = - AstUtils.getAccessorsToTry(target, evalContext.getIndexAccessors()); + AccessorUtils.getAccessorsToTry(target, evalContext.getIndexAccessors()); if (accessMode.supportsReads) { try { for (IndexAccessor indexAccessor : accessorsToTry) { @@ -754,7 +754,7 @@ public class Indexer extends SpelNodeImpl { Indexer.this.cachedPropertyReadState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { if (accessor instanceof ReflectivePropertyAccessor reflectivePropertyAccessor) { @@ -797,7 +797,7 @@ public class Indexer extends SpelNodeImpl { Indexer.this.cachedPropertyWriteState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) { accessor.write(this.evaluationContext, this.targetObject, this.name, newValue); @@ -1014,7 +1014,7 @@ public class Indexer extends SpelNodeImpl { Indexer.this.cachedIndexReadState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); + AccessorUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); for (IndexAccessor indexAccessor : accessorsToTry) { if (indexAccessor.canRead(this.evaluationContext, this.target, this.index)) { TypedValue result = indexAccessor.read(this.evaluationContext, this.target, this.index); @@ -1069,7 +1069,7 @@ public class Indexer extends SpelNodeImpl { Indexer.this.cachedIndexWriteState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); + AccessorUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); for (IndexAccessor indexAccessor : accessorsToTry) { if (indexAccessor.canWrite(this.evaluationContext, this.target, this.index)) { indexAccessor.write(this.evaluationContext, this.target, this.index, newValue); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 5907e2dde01..c40dc8a61dc 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -201,7 +201,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } List accessorsToTry = - AstUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then // get the accessor and use it. If they are not cacheable but report they can read the property // then ask them to read it @@ -259,7 +259,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } List accessorsToTry = - AstUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); try { for (PropertyAccessor accessor : accessorsToTry) { if (accessor.canWrite(evalContext, targetObject, name)) { @@ -284,7 +284,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { Object targetObject = contextObject.getValue(); if (targetObject != null) { List accessorsToTry = - AstUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { try { if (accessor.canWrite(evalContext, targetObject, name)) { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java new file mode 100644 index 00000000000..659a99a87ad --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.ast; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.expression.TargetedAccessor; +import org.springframework.lang.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AccessorUtils}. + * + * @author Sam Brannen + * @since 6.1.15 + */ +class AccessorUtilsTests { + + private final TargetedAccessor animal1Accessor = createAccessor("Animal1", Animal.class); + + private final TargetedAccessor animal2Accessor = createAccessor("Animal2", Animal.class); + + private final TargetedAccessor cat1Accessor = createAccessor("Cat1", Cat.class); + + private final TargetedAccessor cat2Accessor = createAccessor("Cat2", Cat.class); + + private final TargetedAccessor generic1Accessor = createAccessor("Generic1", null); + + private final TargetedAccessor generic2Accessor = createAccessor("Generic2", null); + + private final List accessors = List.of( + generic1Accessor, + cat1Accessor, + animal1Accessor, + animal2Accessor, + cat2Accessor, + generic2Accessor + ); + + + @Test + void emptyAccessorsList() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Cat(), List.of()); + assertThat(accessorsToTry).isEmpty(); + } + + @Test + void noMatch() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Dog(), List.of(cat1Accessor)); + assertThat(accessorsToTry).isEmpty(); + } + + @Test + void singleExactTypeMatch() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Cat(), List.of(cat1Accessor)); + assertThat(accessorsToTry).containsExactly(cat1Accessor); + } + + @Test + void exactTypeSupertypeAndGenericMatches() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Cat(), accessors); + assertThat(accessorsToTry).containsExactly( + cat1Accessor, cat2Accessor, animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); + } + + @Test + void supertypeAndGenericMatches() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Dog(), accessors); + assertThat(accessorsToTry).containsExactly( + animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); + } + + @Test + void genericMatches() { + List accessorsToTry = AccessorUtils.getAccessorsToTry("not an Animal", accessors); + assertThat(accessorsToTry).containsExactly(generic1Accessor, generic2Accessor); + } + + + private static TargetedAccessor createAccessor(String name, Class type) { + return new DemoAccessor(name, (type != null ? new Class[] { type } : null)); + } + + + private record DemoAccessor(String name, Class[] types) implements TargetedAccessor { + + @Override + @Nullable + public Class[] getSpecificTargetClasses() { + return this.types; + } + + @Override + public final String toString() { + return this.name; + } + } + + sealed interface Animal permits Cat, Dog { + } + + static final class Cat implements Animal { + } + + static final class Dog implements Animal { + } + +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java deleted file mode 100644 index d9b2c507ecb..00000000000 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.expression.spel.ast; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypedValue; -import org.springframework.lang.Nullable; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link AstUtils}. - * - * @author Sam Brannen - * @since 6.1.15 - */ -class AstUtilsTests { - - private final PropertyAccessor animal1Accessor = createAccessor("Animal1", Animal.class); - - private final PropertyAccessor animal2Accessor = createAccessor("Animal2", Animal.class); - - private final PropertyAccessor cat1Accessor = createAccessor("Cat1", Cat.class); - - private final PropertyAccessor cat2Accessor = createAccessor("Cat2", Cat.class); - - private final PropertyAccessor generic1Accessor = createAccessor("Generic1", null); - - private final PropertyAccessor generic2Accessor = createAccessor("Generic2", null); - - private final List accessors = List.of( - generic1Accessor, - cat1Accessor, - animal1Accessor, - animal2Accessor, - cat2Accessor, - generic2Accessor - ); - - - @Test - void emptyAccessorsList() { - List accessorsToTry = getPropertyAccessorsToTry(new Cat(), List.of()); - assertThat(accessorsToTry).isEmpty(); - } - - @Test - void noMatch() { - List accessorsToTry = getPropertyAccessorsToTry(new Dog(), List.of(cat1Accessor)); - assertThat(accessorsToTry).isEmpty(); - } - - @Test - void singleExactTypeMatch() { - List accessorsToTry = getPropertyAccessorsToTry(new Cat(), List.of(cat1Accessor)); - assertThat(accessorsToTry).containsExactly(cat1Accessor); - } - - @Test - void exactTypeMatches() { - List accessorsToTry = getPropertyAccessorsToTry(new Cat(), accessors); - assertThat(accessorsToTry).containsExactly( - cat1Accessor, cat2Accessor, animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); - } - - @Test - void supertypeMatches() { - List accessorsToTry = getPropertyAccessorsToTry(new Dog(), accessors); - assertThat(accessorsToTry).containsExactly( - animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); - } - - @Test - void genericMatches() { - List accessorsToTry = getPropertyAccessorsToTry("not an Animal", accessors); - assertThat(accessorsToTry).containsExactly(generic1Accessor, generic2Accessor); - } - - - private static PropertyAccessor createAccessor(String name, Class type) { - return new DemoAccessor(name, type); - } - - private static List getPropertyAccessorsToTry(Object target, List propertyAccessors) { - return AstUtils.getAccessorsToTry(target, propertyAccessors); - } - - - private static class DemoAccessor implements PropertyAccessor { - - private final String name; - private final Class[] types; - - DemoAccessor(String name, Class type) { - this.name = name; - this.types = (type != null ? new Class[] {type} : null); - } - - @Override - @Nullable - public Class[] getSpecificTargetClasses() { - return this.types; - } - - @Override - public String toString() { - return this.name; - } - - @Override - public boolean canRead(EvaluationContext context, Object target, String name) { - return true; - } - - @Override - public TypedValue read(EvaluationContext context, Object target, String name) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - @Override - public boolean canWrite(EvaluationContext context, Object target, String name) { - return false; - } - - @Override - public void write(EvaluationContext context, Object target, String name, Object newValue) { - /* no-op */ - } - } - - sealed interface Animal permits Bat, Cat, Dog { - } - - static final class Bat implements Animal { - } - - static final class Cat implements Animal { - } - - static final class Dog implements Animal { - } - -}