From 9a38355896fa062f67ebca4cad00ad09479277b2 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:46:13 +0100 Subject: [PATCH] Improve tests for indexing and collection selection/projection in SpEL --- .../expression/spel/IndexingTests.java | 233 ++++----- .../spel/SelectionAndProjectionTests.java | 455 +++++++++--------- 2 files changed, 343 insertions(+), 345 deletions(-) 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 800d769bbd5..03448e5dda1 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 @@ -30,7 +30,6 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.expression.EvaluationContext; -import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; @@ -38,6 +37,9 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.expression.spel.SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE; +import static org.springframework.expression.spel.SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE; @SuppressWarnings("rawtypes") class IndexingTests { @@ -50,16 +52,14 @@ class IndexingTests { this.property = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.HashMap", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); assertThat(expression.getValue(this, Map.class)).isEqualTo(property); expression = parser.parseExpression("property['foo']"); assertThat(expression.getValue(this)).isEqualTo("bar"); } - @FieldAnnotation - public Object property; - @Test @SuppressWarnings("unchecked") void indexIntoGenericPropertyContainingMapObject() { @@ -72,43 +72,14 @@ class IndexingTests { context.addPropertyAccessor(new MapAccessor()); context.setRootObject(property); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(context).toString()).isEqualTo("java.util.HashMap"); + assertThat(expression.getValueTypeDescriptor(context)).asString() + .isEqualTo("java.util.HashMap"); assertThat(expression.getValue(context)).isEqualTo(map); assertThat(expression.getValue(context, Map.class)).isEqualTo(map); expression = parser.parseExpression("property['foo']"); assertThat(expression.getValue(context)).isEqualTo("bar"); } - public static class MapAccessor implements PropertyAccessor { - - @Override - public boolean canRead(EvaluationContext context, Object target, String name) { - return (((Map) target).containsKey(name)); - } - - @Override - public TypedValue read(EvaluationContext context, Object target, String name) { - return new TypedValue(((Map) target).get(name)); - } - - @Override - public boolean canWrite(EvaluationContext context, Object target, String name) { - return true; - } - - @Override - @SuppressWarnings("unchecked") - public void write(EvaluationContext context, Object target, String name, Object newValue) { - ((Map) target).put(name, newValue); - } - - @Override - public Class[] getSpecificTargetClasses() { - return new Class[] {Map.class}; - } - - } - @Test void setGenericPropertyContainingMap() { Map property = new HashMap<>(); @@ -116,7 +87,8 @@ class IndexingTests { this.property = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.HashMap", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("property['foo']"); assertThat(expression.getValue(this)).isEqualTo("bar"); @@ -131,7 +103,8 @@ class IndexingTests { this.parameterizedMap = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("parameterizedMap"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.HashMap"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("java.util.HashMap"); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("parameterizedMap['9']"); assertThat(expression.getValue(this)).isEqualTo(3); @@ -139,14 +112,13 @@ class IndexingTests { assertThat(expression.getValue(this)).isEqualTo(37); } - public Map parameterizedMap; - @Test void setPropertyContainingMapAutoGrow() { SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, false)); Expression expression = parser.parseExpression("parameterizedMap"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.Map"); - assertThat(expression.getValue(this)).isEqualTo(property); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("java.util.Map"); + assertThat(expression.getValue(this)).isNull(); expression = parser.parseExpression("parameterizedMap['9']"); assertThat(expression.getValue(this)).isNull(); expression.setValue(this, "37"); @@ -160,7 +132,8 @@ class IndexingTests { this.property = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("property[0]"); assertThat(expression.getValue(this)).isEqualTo("bar"); @@ -173,7 +146,8 @@ class IndexingTests { this.property = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("property[0]"); assertThat(expression.getValue(this)).isEqualTo(3); @@ -187,18 +161,15 @@ class IndexingTests { this.property = property; SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); - expression = parser.parseExpression("property[0]"); - try { - expression.setValue(this, "4"); - } - catch (EvaluationException ex) { - assertThat(ex.getMessage()).startsWith("EL1053E"); - } - } - public List decimals; + Expression indexExpression = parser.parseExpression("property[0]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE)); + } @Test void autoGrowListOfElementsWithoutDefaultConstructor() { @@ -225,14 +196,13 @@ class IndexingTests { this.parameterizedList = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("parameterizedList"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("java.util.ArrayList"); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("parameterizedList[0]"); assertThat(expression.getValue(this)).isEqualTo(3); } - public List parameterizedList; - @Test void indexIntoPropertyContainingListOfList() { List> property = new ArrayList<>(); @@ -240,14 +210,13 @@ class IndexingTests { this.parameterizedListOfList = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("parameterizedListOfList[0]"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.Arrays$ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("java.util.Arrays$ArrayList"); assertThat(expression.getValue(this)).isEqualTo(property.get(0)); expression = parser.parseExpression("parameterizedListOfList[0][0]"); assertThat(expression.getValue(this)).isEqualTo(3); } - public List> parameterizedListOfList; - @Test void setPropertyContainingList() { List property = new ArrayList<>(); @@ -255,7 +224,8 @@ class IndexingTests { this.parameterizedList = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("parameterizedList"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("java.util.ArrayList"); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("parameterizedList[0]"); assertThat(expression.getValue(this)).isEqualTo(3); @@ -268,15 +238,14 @@ class IndexingTests { SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.lang.Object"); - assertThat(expression.getValue(this)).isEqualTo(property); - expression = parser.parseExpression("property[0]"); - try { - assertThat(expression.getValue(this)).isEqualTo("bar"); - } - catch (EvaluationException ex) { - assertThat(ex.getMessage()).startsWith("EL1027E"); - } + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.lang.Object", FieldAnnotation.class.getName()); + assertThat(expression.getValue(this)).isNull(); + + Expression indexExpression = parser.parseExpression("property[0]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(INDEXING_NOT_SUPPORTED_FOR_TYPE)); } @Test @@ -286,15 +255,14 @@ class IndexingTests { SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); - expression = parser.parseExpression("property[0]"); - try { - assertThat(expression.getValue(this)).isEqualTo("bar"); - } - catch (EvaluationException ex) { - assertThat(ex.getMessage()).startsWith("EL1053E"); - } + + Expression indexExpression = parser.parseExpression("property[0]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE)); } @Test @@ -304,18 +272,14 @@ class IndexingTests { SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property2"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString().isEqualTo("java.util.ArrayList"); assertThat(expression.getValue(this)).isEqualTo(property2); - expression = parser.parseExpression("property2[0]"); - try { - assertThat(expression.getValue(this)).isEqualTo("bar"); - } - catch (EvaluationException ex) { - assertThat(ex.getMessage()).startsWith("EL1053E"); - } - } - public List property2; + Expression indexExpression = parser.parseExpression("property2[0]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE)); + } @Test void indexIntoGenericPropertyContainingArray() { @@ -323,7 +287,8 @@ class IndexingTests { this.property = property; SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("property"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.lang.String[]"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.lang.String[]", FieldAnnotation.class.getName()); assertThat(expression.getValue(this)).isEqualTo(property); expression = parser.parseExpression("property[0]"); assertThat(expression.getValue(this)).isEqualTo("bar"); @@ -334,19 +299,20 @@ class IndexingTests { listOfScalarNotGeneric = new ArrayList(); SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("listOfScalarNotGeneric"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString().isEqualTo("java.util.ArrayList"); assertThat(expression.getValue(this, String.class)).isEmpty(); } - @SuppressWarnings("unchecked") @Test + @SuppressWarnings("unchecked") void resolveCollectionElementType() { listNotGeneric = new ArrayList(2); listNotGeneric.add(5); listNotGeneric.add(6); SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("listNotGeneric"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.ArrayList"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getName()); assertThat(expression.getValue(this, String.class)).isEqualTo("5,6"); } @@ -354,16 +320,8 @@ class IndexingTests { void resolveCollectionElementTypeNull() { SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("listNotGeneric"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.List"); - } - - @FieldAnnotation - public List listNotGeneric; - - @Target({ElementType.FIELD}) - @Retention(RetentionPolicy.RUNTIME) - public @interface FieldAnnotation { - + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.List", FieldAnnotation.class.getName()); } @SuppressWarnings("unchecked") @@ -374,14 +332,12 @@ class IndexingTests { mapNotGeneric.put("bonusAmount", 7.17); SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("mapNotGeneric"); - assertThat(expression.getValueTypeDescriptor(this).toString()).isEqualTo("@org.springframework.expression.spel.IndexingTests$FieldAnnotation java.util.HashMap"); + assertThat(expression.getValueTypeDescriptor(this)).asString() + .isEqualTo("@%s java.util.HashMap", FieldAnnotation.class.getName()); } - @FieldAnnotation - public Map mapNotGeneric; - - @SuppressWarnings("unchecked") @Test + @SuppressWarnings("unchecked") void testListOfScalar() { listOfScalarNotGeneric = new ArrayList(1); listOfScalarNotGeneric.add("5"); @@ -390,11 +346,8 @@ class IndexingTests { assertThat(expression.getValue(this, Integer.class)).isEqualTo(Integer.valueOf(5)); } - public List listOfScalarNotGeneric; - - - @SuppressWarnings("unchecked") @Test + @SuppressWarnings("unchecked") void testListsOfMap() { listOfMapsNotGeneric = new ArrayList(); Map map = new HashMap(); @@ -405,6 +358,64 @@ class IndexingTests { assertThat(expression.getValue(this, String.class)).isEqualTo("apple"); } + + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.RUNTIME) + @interface FieldAnnotation { + } + + @FieldAnnotation + public Object property; + + public List property2; + + public Map parameterizedMap; + + public List decimals; + + public List parameterizedList; + + public List> parameterizedListOfList; + + @FieldAnnotation + public List listNotGeneric; + + @FieldAnnotation + public Map mapNotGeneric; + + public List listOfScalarNotGeneric; + public List listOfMapsNotGeneric; + + private static class MapAccessor implements PropertyAccessor { + + @Override + public boolean canRead(EvaluationContext context, Object target, String name) { + return (((Map) target).containsKey(name)); + } + + @Override + public TypedValue read(EvaluationContext context, Object target, String name) { + return new TypedValue(((Map) target).get(name)); + } + + @Override + public boolean canWrite(EvaluationContext context, Object target, String name) { + return true; + } + + @Override + @SuppressWarnings("unchecked") + public void write(EvaluationContext context, Object target, String name, Object newValue) { + ((Map) target).put(name, newValue); + } + + @Override + public Class[] getSpecificTargetClasses() { + return new Class[] {Map.class}; + } + + } + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java index bd9e9f9ecf8..80f67b68340 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java @@ -17,18 +17,22 @@ package org.springframework.expression.spel; import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; -import org.springframework.expression.TypedValue; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -36,6 +40,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.INTEGER; import static org.assertj.core.api.InstanceOfAssertFactories.LIST; +import static org.assertj.core.api.InstanceOfAssertFactories.array; import static org.springframework.expression.spel.SpelMessage.INVALID_TYPE_FOR_SELECTION; import static org.springframework.expression.spel.SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE; import static org.springframework.expression.spel.SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN; @@ -46,241 +51,244 @@ import static org.springframework.expression.spel.SpelMessage.RESULT_OF_SELECTIO * @author Juergen Hoeller * @author Andy Clement */ -class SelectionAndProjectionTests extends AbstractExpressionTests { +class SelectionAndProjectionTests { - @Test - void selectionOnUnsupportedType() { - evaluateAndCheckError("'abc'.?[#this<5]", INVALID_TYPE_FOR_SELECTION); - evaluateAndCheckError("null.?[#this<5]", INVALID_TYPE_FOR_SELECTION); - } + @Nested + class SelectionTests extends AbstractExpressionTests { - @Test - void projectionOnUnsupportedType() { - evaluateAndCheckError("'abc'.![true]", PROJECTION_NOT_SUPPORTED_ON_TYPE); - evaluateAndCheckError("null.![true]", PROJECTION_NOT_SUPPORTED_ON_TYPE); - } + @Test + void selectionOnUnsupportedType() { + evaluateAndCheckError("'abc'.?[#this < 5]", INVALID_TYPE_FOR_SELECTION); + evaluateAndCheckError("null.?[#this < 5]", INVALID_TYPE_FOR_SELECTION); + } - @Test - void selectionOnNullWithSafeNavigation() { - evaluate("null?.?[#this<5]", null, null); - } + @Test + void selectionWithNonBooleanSelectionCriteria() { + evaluateAndCheckError("mapOfNumbersUpToTen.?['hello']", RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN); + evaluateAndCheckError("mapOfNumbersUpToTen.keySet().?['hello']", RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN); + } - @Test - void projectionOnNullWithSafeNavigation() { - evaluate("null?.![true]", null, null); - } + @Test + void selectionWithSafeNavigation() { + evaluate("null?.?[true]", null, null); + evaluate("{1,2,3}?.?[true]", List.of(1, 2, 3), ArrayList.class); + evaluate("{1,2,3,4,5,6}?.?[#this between {2, 4}]", List.of(2, 3, 4), ArrayList.class); + } - @Test - void selectionWithNonBooleanSelectionCriteria() { - evaluateAndCheckError("mapOfNumbersUpToTen.?['hello']", RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN); - evaluateAndCheckError("mapOfNumbersUpToTen.keySet().?['hello']", RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN); - } + @Test + void selectionAST() { + // select first + SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.^[true]"); + assertThat(expr.toStringAST()).isEqualTo("'abc'.^[true]"); - @Test - void selectionAST() { - // select first - SpelExpression expr = (SpelExpression) parser.parseExpression("'abc'.^[true]"); - assertThat(expr.toStringAST()).isEqualTo("'abc'.^[true]"); + // select all + expr = (SpelExpression) parser.parseExpression("'abc'.?[true]"); + assertThat(expr.toStringAST()).isEqualTo("'abc'.?[true]"); - // select all - expr = (SpelExpression) parser.parseExpression("'abc'.?[true]"); - assertThat(expr.toStringAST()).isEqualTo("'abc'.?[true]"); + // select last + expr = (SpelExpression) parser.parseExpression("'abc'.$[true]"); + assertThat(expr.toStringAST()).isEqualTo("'abc'.$[true]"); + } - // select last - expr = (SpelExpression) parser.parseExpression("'abc'.$[true]"); - assertThat(expr.toStringAST()).isEqualTo("'abc'.$[true]"); - } + @Test + @SuppressWarnings("unchecked") + void selectionWithList() { + Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ListTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(0, 1, 2, 3, 4); + } - @Test - @SuppressWarnings("unchecked") - void selectionWithList() { - Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ListTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(0, 1, 2, 3, 4); - } + @Test + void selectFirstItemInList() { + Expression expression = new SpelExpressionParser().parseRaw("integers.^[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ListTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); + } - @Test - void selectFirstItemInList() { - Expression expression = new SpelExpressionParser().parseRaw("integers.^[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ListTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); - } + @Test + void selectLastItemInList() { + Expression expression = new SpelExpressionParser().parseRaw("integers.$[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ListTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); + } - @Test - void selectLastItemInList() { - Expression expression = new SpelExpressionParser().parseRaw("integers.$[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ListTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); - } + @Test + void selectionWithSetAndRegex() { + evaluate("testMap.keySet().?[#this matches '.*o.*']", "[monday]", ArrayList.class); + evaluate("testMap.keySet().?[#this matches '.*r.*'].contains('saturday')", "true", Boolean.class); + evaluate("testMap.keySet().?[#this matches '.*r.*'].size()", "3", Integer.class); + } - @Test - void selectionWithSetAndRegex() { - evaluate("testMap.keySet().?[#this matches '.*o.*']", "[monday]", ArrayList.class); - evaluate("testMap.keySet().?[#this matches '.*r.*'].contains('saturday')", "true", Boolean.class); - evaluate("testMap.keySet().?[#this matches '.*r.*'].size()", "3", Integer.class); - } + @Test + @SuppressWarnings("unchecked") + void selectionWithSet() { + Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new SetTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(0, 1, 2, 3, 4); + } - @Test - @SuppressWarnings("unchecked") - void selectionWithSet() { - Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new SetTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(0, 1, 2, 3, 4); - } + @Test + void selectFirstItemInSet() { + Expression expression = new SpelExpressionParser().parseRaw("integers.^[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new SetTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); + } - @Test - void selectFirstItemInSet() { - Expression expression = new SpelExpressionParser().parseRaw("integers.^[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new SetTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); - } + @Test + void selectLastItemInSet() { + Expression expression = new SpelExpressionParser().parseRaw("integers.$[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new SetTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); + } - @Test - void selectLastItemInSet() { - Expression expression = new SpelExpressionParser().parseRaw("integers.$[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new SetTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); - } + @ParameterizedTest + @ValueSource(strings = {"#root.?[#this < 3]", "?[#this < 3]"}) + void selectionWithIterable(String expressionString) { + Expression expression = new SpelExpressionParser().parseRaw(expressionString); + EvaluationContext context = new StandardEvaluationContext(new IterableTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(1, 2); + } - @Test - @SuppressWarnings("unchecked") - void selectionWithIterable() { - Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new IterableTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(0, 1, 2, 3, 4); - } + @Test + void selectionWithArray() { + Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); + Object value = expression.getValue(context); + assertThat(value).asInstanceOf(array(Integer[].class)).containsExactly(0, 1, 2, 3, 4); + } - @Test - void selectionWithArray() { - Expression expression = new SpelExpressionParser().parseRaw("integers.?[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); - Object value = expression.getValue(context); - assertThat(value.getClass().isArray()).isTrue(); - TypedValue typedValue = new TypedValue(value); - assertThat(typedValue.getTypeDescriptor().getElementTypeDescriptor().getType()).isEqualTo(Integer.class); - assertThat((Integer[]) value).containsExactly(0, 1, 2, 3, 4); - } + @Test + void selectFirstItemInArray() { + Expression expression = new SpelExpressionParser().parseRaw("integers.^[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); + } - @Test - void selectFirstItemInArray() { - Expression expression = new SpelExpressionParser().parseRaw("integers.^[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); - } + @Test + void selectLastItemInArray() { + Expression expression = new SpelExpressionParser().parseRaw("integers.$[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); + } - @Test - void selectLastItemInArray() { - Expression expression = new SpelExpressionParser().parseRaw("integers.$[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); - } + @Test + void selectionWithPrimitiveArray() { + Expression expression = new SpelExpressionParser().parseRaw("ints.?[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); + Object value = expression.getValue(context); + assertThat(value).asInstanceOf(array(Integer[].class)).containsExactly(0, 1, 2, 3, 4); + } - @Test - void selectionWithPrimitiveArray() { - Expression expression = new SpelExpressionParser().parseRaw("ints.?[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); - Object value = expression.getValue(context); - assertThat(value.getClass().isArray()).isTrue(); - TypedValue typedValue = new TypedValue(value); - assertThat(typedValue.getTypeDescriptor().getElementTypeDescriptor().getType()).isEqualTo(Integer.class); - assertThat((Integer[]) value).containsExactly(0, 1, 2, 3, 4); - } + @Test + void selectFirstItemInPrimitiveArray() { + Expression expression = new SpelExpressionParser().parseRaw("ints.^[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); + } - @Test - void selectFirstItemInPrimitiveArray() { - Expression expression = new SpelExpressionParser().parseRaw("ints.^[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isZero(); - } + @Test + void selectLastItemInPrimitiveArray() { + Expression expression = new SpelExpressionParser().parseRaw("ints.$[#this < 5]"); + EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); + } - @Test - void selectLastItemInPrimitiveArray() { - Expression expression = new SpelExpressionParser().parseRaw("ints.$[#this<5]"); - EvaluationContext context = new StandardEvaluationContext(new ArrayTestBean()); - assertThat(expression.getValue(context)).asInstanceOf(INTEGER).isEqualTo(4); - } + @Test + @SuppressWarnings("unchecked") + void selectionWithMap() { + EvaluationContext context = new StandardEvaluationContext(new MapTestBean()); + ExpressionParser parser = new SpelExpressionParser(); - @Test - @SuppressWarnings("unchecked") - void selectionWithMap() { - EvaluationContext context = new StandardEvaluationContext(new MapTestBean()); - ExpressionParser parser = new SpelExpressionParser(); + Expression exp = parser.parseExpression("colors.?[key.startsWith('b')]"); + Map colorsMap = (Map) exp.getValue(context); + assertThat(colorsMap).containsOnlyKeys("beige", "blue", "brown"); - Expression exp = parser.parseExpression("colors.?[key.startsWith('b')]"); - Map colorsMap = (Map) exp.getValue(context); - assertThat(colorsMap).containsOnlyKeys("beige", "blue", "brown"); + exp = parser.parseExpression("colors.?[key.startsWith('X')]"); - exp = parser.parseExpression("colors.?[key.startsWith('X')]"); + colorsMap = (Map) exp.getValue(context); + assertThat(colorsMap).isEmpty(); + } - colorsMap = (Map) exp.getValue(context); - assertThat(colorsMap).isEmpty(); - } + @Test + @SuppressWarnings("unchecked") + void selectFirstItemInMap() { + EvaluationContext context = new StandardEvaluationContext(new MapTestBean()); + ExpressionParser parser = new SpelExpressionParser(); - @Test - @SuppressWarnings("unchecked") - void selectFirstItemInMap() { - EvaluationContext context = new StandardEvaluationContext(new MapTestBean()); - ExpressionParser parser = new SpelExpressionParser(); + Expression exp = parser.parseExpression("colors.^[key.startsWith('b')]"); + Map colorsMap = (Map) exp.getValue(context); + assertThat(colorsMap).containsOnlyKeys("beige"); + } - Expression exp = parser.parseExpression("colors.^[key.startsWith('b')]"); - Map colorsMap = (Map) exp.getValue(context); - assertThat(colorsMap).containsOnlyKeys("beige"); - } + @Test + @SuppressWarnings("unchecked") + void selectLastItemInMap() { + EvaluationContext context = new StandardEvaluationContext(new MapTestBean()); + ExpressionParser parser = new SpelExpressionParser(); - @Test - @SuppressWarnings("unchecked") - void selectLastItemInMap() { - EvaluationContext context = new StandardEvaluationContext(new MapTestBean()); - ExpressionParser parser = new SpelExpressionParser(); + Expression exp = parser.parseExpression("colors.$[key.startsWith('b')]"); + Map colorsMap = (Map) exp.getValue(context); + assertThat(colorsMap).containsOnlyKeys("brown"); + } - Expression exp = parser.parseExpression("colors.$[key.startsWith('b')]"); - Map colorsMap = (Map) exp.getValue(context); - assertThat(colorsMap).containsOnlyKeys("brown"); } - @Test - @SuppressWarnings("unchecked") - void projectionWithList() { - Expression expression = new SpelExpressionParser().parseRaw("#testList.![wrapper.value]"); - EvaluationContext context = new StandardEvaluationContext(); - context.setVariable("testList", IntegerTestBean.createList()); - assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(5, 6, 7); - } + @Nested + class ProjectionTests extends AbstractExpressionTests { - @Test - void projectionWithMap() { - evaluate("mapOfNumbersUpToTen.![key > 5 ? value : null]", + @Test + void projectionOnUnsupportedType() { + evaluateAndCheckError("'abc'.![true]", PROJECTION_NOT_SUPPORTED_ON_TYPE); + evaluateAndCheckError("null.![true]", PROJECTION_NOT_SUPPORTED_ON_TYPE); + } + + @Test + void projectionWithSafeNavigation() { + evaluate("null?.![#this]", null, null); + evaluate("{1,2,3}?.![#this % 2]", List.of(1, 0, 1), ArrayList.class); + } + + @Test + @SuppressWarnings("unchecked") + void projectionWithList() { + Expression expression = new SpelExpressionParser().parseRaw("#testList.![wrapper.value]"); + EvaluationContext context = new StandardEvaluationContext(); + context.setVariable("testList", IntegerTestBean.createList()); + assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(5, 6, 7); + } + + @Test + void projectionWithMap() { + evaluate("mapOfNumbersUpToTen.![key > 5 ? value : null]", "[null, null, null, null, null, six, seven, eight, nine, ten]", ArrayList.class); - } + } - @Test - @SuppressWarnings("unchecked") - void projectionWithSet() { - Expression expression = new SpelExpressionParser().parseRaw("#testList.![wrapper.value]"); - EvaluationContext context = new StandardEvaluationContext(); - context.setVariable("testList", IntegerTestBean.createSet()); - assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(5, 6, 7); - } + @Test + @SuppressWarnings("unchecked") + void projectionWithSet() { + Expression expression = new SpelExpressionParser().parseRaw("#testSet.![wrapper.value]"); + EvaluationContext context = new StandardEvaluationContext(); + context.setVariable("testSet", IntegerTestBean.createSet()); + assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(5, 6, 7); + } - @Test - @SuppressWarnings("unchecked") - void projectionWithIterable() { - Expression expression = new SpelExpressionParser().parseRaw("#testList.![wrapper.value]"); - EvaluationContext context = new StandardEvaluationContext(); - context.setVariable("testList", IntegerTestBean.createIterable()); - assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(5, 6, 7); - } + @ParameterizedTest + @ValueSource(strings = {"#root.![#this * 2]", "![#this * 2]"}) + void projectionWithIterable(String expressionString) { + Expression expression = new SpelExpressionParser().parseRaw(expressionString); + EvaluationContext context = new StandardEvaluationContext(new IterableTestBean()); + assertThat(expression.getValue(context)).asInstanceOf(LIST).containsExactly(2, 4, 6, 8, 10); + } + + @Test + void projectionWithArray() { + Expression expression = new SpelExpressionParser().parseRaw("#testArray.![wrapper.value]"); + EvaluationContext context = new StandardEvaluationContext(); + context.setVariable("testArray", IntegerTestBean.createArray()); + Object value = expression.getValue(context); + assertThat(value).asInstanceOf(array(Number[].class)).containsExactly(5, 5.9f, 7); + } - @Test - void projectionWithArray() { - Expression expression = new SpelExpressionParser().parseRaw("#testArray.![wrapper.value]"); - EvaluationContext context = new StandardEvaluationContext(); - context.setVariable("testArray", IntegerTestBean.createArray()); - Object value = expression.getValue(context); - assertThat(value.getClass().isArray()).isTrue(); - TypedValue typedValue = new TypedValue(value); - assertThat(typedValue.getTypeDescriptor().getElementTypeDescriptor().getType()).isEqualTo(Number.class); - assertThat((Number[]) value).containsExactly(5, 5.9f, 7); } @@ -288,7 +296,7 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { private final List integers = new ArrayList<>(); - ListTestBean() { + { for (int i = 0; i < 10; i++) { integers.add(i); } @@ -304,7 +312,7 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { private final Set integers = new LinkedHashSet<>(); - SetTestBean() { + { for (int i = 0; i < 10; i++) { integers.add(i); } @@ -316,18 +324,13 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { } - static class IterableTestBean { + static class IterableTestBean implements Iterable { - private final Set integers = new LinkedHashSet<>(); + private final Collection integers = List.of(1, 2, 3, 4, 5); - IterableTestBean() { - for (int i = 0; i < 10; i++) { - integers.add(i); - } - } - - public Iterable getIntegers() { - return integers; + @Override + public Iterator iterator() { + return integers.iterator(); } } @@ -338,7 +341,7 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { private final Integer[] integers = new Integer[10]; - ArrayTestBean() { + { for (int i = 0; i < 10; i++) { ints[i] = i; integers[i] = i; @@ -359,8 +362,7 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { private final Map colors = new TreeMap<>(); - MapTestBean() { - // colors.put("black", "schwarz"); + { colors.put("red", "rot"); colors.put("brown", "braun"); colors.put("blue", "blau"); @@ -376,13 +378,13 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { static class IntegerTestBean { - private final IntegerWrapper wrapper; + private final NumberWrapper wrapper; IntegerTestBean(Number value) { - this.wrapper = new IntegerWrapper(value); + this.wrapper = new NumberWrapper(value); } - public IntegerWrapper getWrapper() { + public NumberWrapper getWrapper() { return this.wrapper; } @@ -402,11 +404,6 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { return set; } - static Iterable createIterable() { - final Set set = createSet(); - return set; - } - static IntegerTestBean[] createArray() { IntegerTestBean[] array = new IntegerTestBean[3]; for (int i = 0; i < 3; i++) { @@ -422,17 +419,7 @@ class SelectionAndProjectionTests extends AbstractExpressionTests { } - static class IntegerWrapper { - - private final Number value; - - IntegerWrapper(Number value) { - this.value = value; - } - - public Number getValue() { - return this.value; - } - } + record NumberWrapper(Number value) { + }; }