From 347d08502059acb7dfe8862d394b3d285dbf498b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:34:20 +0100 Subject: [PATCH] Polishing --- .../language-ref/collection-projection.adoc | 4 +- .../operator-safe-navigation.adoc | 42 ++++++++++++------- .../expression/spel/EvaluationTests.java | 4 +- .../spel/MethodInvocationTests.java | 2 +- .../expression/spel/OperatorTests.java | 17 +++++++- .../expression/spel/PropertyAccessTests.java | 2 +- .../spel/SpelDocumentationTests.java | 33 ++++++++++++--- .../expression/spel/TestScenarioCreator.java | 2 +- 8 files changed, 76 insertions(+), 30 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc index efc43fe8c1..5c16426a36 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/collection-projection.adoc @@ -13,7 +13,7 @@ Java:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - // evaluates to ["SmilJan", "Idvor"] + // evaluates to ["Smiljan", "Idvor"] List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") .getValue(societyContext, List.class); ---- @@ -22,7 +22,7 @@ Kotlin:: + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- - // evaluates to ["SmilJan", "Idvor"] + // evaluates to ["Smiljan", "Idvor"] val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") .getValue(societyContext) as List<*> ---- diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc index 0691cf2de8..22a9aad4e6 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/operator-safe-navigation.adoc @@ -1,12 +1,14 @@ [[expressions-operator-safe-navigation]] = Safe Navigation Operator -The safe navigation operator is used to avoid a `NullPointerException` and comes from -the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy] -language. Typically, when you have a reference to an object, you might need to verify that -it is not null before accessing methods or properties of the object. To avoid this, the -safe navigation operator returns null instead of throwing an exception. The following -example shows how to use the safe navigation operator: +The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes +from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy] +language. Typically, when you have a reference to an object, you might need to verify +that it is not `null` before accessing methods or properties of the object. To avoid +this, the safe navigation operator returns `null` instead of throwing an exception. + +The following example shows how to use the safe navigation operator for property access +(`?.`). [tabs] ====== @@ -20,13 +22,18 @@ Java:: Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); - String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); - System.out.println(city); // Smiljan + // evaluates to "Smiljan" + String city = parser.parseExpression("placeOfBirth?.city") // <1> + .getValue(context, tesla, String.class); tesla.setPlaceOfBirth(null); - city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); - System.out.println(city); // null - does not throw NullPointerException!!! + + // evaluates to null - does not throw NullPointerException + city = parser.parseExpression("placeOfBirth?.city") // <2> + .getValue(context, tesla, String.class); ---- +<1> Use safe navigation operator on non-null `placeOfBirth` property +<2> Use safe navigation operator on null `placeOfBirth` property Kotlin:: + @@ -38,14 +45,17 @@ Kotlin:: val tesla = Inventor("Nikola Tesla", "Serbian") tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan")) - var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java) - println(city) // Smiljan + // evaluates to "Smiljan" + var city = parser.parseExpression("placeOfBirth?.city") // <1> + .getValue(context, tesla, String::class.java) tesla.setPlaceOfBirth(null) - city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java) - println(city) // null - does not throw NullPointerException!!! + + // evaluates to null - does not throw NullPointerException + city = parser.parseExpression("placeOfBirth?.city") // <2> + .getValue(context, tesla, String::class.java) ---- +<1> Use safe navigation operator on non-null `placeOfBirth` property +<2> Use safe navigation operator on null `placeOfBirth` property ====== - - diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 62b2a53e89..c0733d0723 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -234,7 +234,7 @@ class EvaluationTests extends AbstractExpressionTests { @Test void stringType() { - evaluateAndAskForReturnType("getPlaceOfBirth().getCity()", "SmilJan", String.class); + evaluateAndAskForReturnType("getPlaceOfBirth().getCity()", "Smiljan", String.class); } @Test @@ -594,7 +594,7 @@ class EvaluationTests extends AbstractExpressionTests { // nested properties @Test void propertiesNested01() { - evaluate("placeOfBirth.city", "SmilJan", String.class, true); + evaluate("placeOfBirth.city", "Smiljan", String.class, true); } @Test diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java index 65e492932b..626db90a46 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java @@ -49,7 +49,7 @@ class MethodInvocationTests extends AbstractExpressionTests { @Test void testSimpleAccess01() { - evaluate("getPlaceOfBirth().getCity()", "SmilJan", String.class); + evaluate("getPlaceOfBirth().getCity()", "Smiljan", String.class); } @Test diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index cb66885d8c..1766325e1d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * 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. @@ -16,6 +16,7 @@ package org.springframework.expression.spel; +import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; @@ -24,6 +25,8 @@ import org.junit.jupiter.api.Test; import org.springframework.expression.Expression; import org.springframework.expression.spel.ast.Operator; import org.springframework.expression.spel.standard.SpelExpression; +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.springframework.expression.spel.SpelMessage.MAX_CONCATENATED_STRING_LENGTH_EXCEEDED; @@ -670,6 +673,18 @@ class OperatorTests extends AbstractExpressionTests { evaluate("new java.math.BigInteger('5') ^ 3", new BigInteger("125"), BigInteger.class); } + @Test + void bigIntFunction() throws Exception { + SpelExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + Method method = BigInteger.class.getMethod("valueOf", long.class); + context.registerFunction("bigInt", method); + + Expression expression = parser.parseExpression("3 + #bigInt(5)"); + BigInteger result = expression.getValue(context, BigInteger.class); + assertThat(result).isEqualTo(BigInteger.valueOf(8)); + } + private Operator getOperatorNode(SpelExpression expr) { SpelNode node = expr.getAST(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java index 46d132cfc6..287644f845 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java @@ -58,7 +58,7 @@ class PropertyAccessTests extends AbstractExpressionTests { @Test void simpleAccess02() { - evaluate("placeOfBirth.city", "SmilJan", String.class); + evaluate("placeOfBirth.city", "Smiljan", String.class); } @Test diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index a1c66f7456..4456208c42 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -65,7 +65,7 @@ class SpelDocumentationTests extends AbstractExpressionTests { GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); - tesla.setPlaceOfBirth(new PlaceOfBirth("SmilJan")); + tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); tesla.setInventions("Telephone repeater", "Rotating magnetic field principle", "Polyphase alternating-current system", "Induction motor", "Alternating-current power transmission", "Tesla coil transformer", "Wireless communication", "Radio", "Fluorescent lights"); @@ -167,7 +167,7 @@ class SpelDocumentationTests extends AbstractExpressionTests { assertThat(year).isEqualTo(1856); String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); - assertThat(city).isEqualTo("SmilJan"); + assertThat(city).isEqualTo("Smiljan"); } @Test @@ -616,11 +616,10 @@ class SpelDocumentationTests extends AbstractExpressionTests { StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); - parser.parseExpression("Name").setValue(societyContext, "IEEE"); societyContext.setVariable("queryName", "Nikola Tesla"); - String expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + String expression = "isMember(#queryName) ? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; String queryResultString = parser.parseExpression(expression).getValue(societyContext, String.class); @@ -629,6 +628,28 @@ class SpelDocumentationTests extends AbstractExpressionTests { } } + @Nested + class SaveNavigationOperator { + + @Test + void nullSafePropertyAccess() { + Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); + tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); + + // evaluates to "Smiljan" + String city = parser.parseExpression("placeOfBirth?.city") // <1> + .getValue(context, tesla, String.class); + assertThat(city).isEqualTo("Smiljan"); + + tesla.setPlaceOfBirth(null); + + // evaluates to null - does not throw a NullPointerException + city = parser.parseExpression("placeOfBirth?.city") // <2> + .getValue(context, tesla, String.class); + assertThat(city).isNull(); + } + } + @Nested class CollectionSelection { @@ -650,10 +671,10 @@ class SpelDocumentationTests extends AbstractExpressionTests { @SuppressWarnings("unchecked") void projection() { StandardEvaluationContext societyContext = new StandardEvaluationContext(new IEEE()); - // evaluates to ["SmilJan", "Idvor"] + // evaluates to ["Smiljan", "Idvor"] List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") .getValue(societyContext, List.class); - assertThat(placesOfBirth).containsExactly("SmilJan", "Idvor"); + assertThat(placesOfBirth).containsExactly("Smiljan", "Idvor"); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java index ca36cc706e..614fcf431d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java @@ -119,7 +119,7 @@ class TestScenarioCreator { GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); - tesla.setPlaceOfBirth(new PlaceOfBirth("SmilJan")); + tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); tesla.setInventions("Telephone repeater", "Rotating magnetic field principle", "Polyphase alternating-current system", "Induction motor", "Alternating-current power transmission", "Tesla coil transformer", "Wireless communication", "Radio", "Fluorescent lights");