Polishing

This commit is contained in:
Sam Brannen 2024-02-11 15:34:20 +01:00
parent f295def2a8
commit 347d085020
8 changed files with 76 additions and 30 deletions

View File

@ -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<*>
----

View File

@ -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
======

View File

@ -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

View File

@ -49,7 +49,7 @@ class MethodInvocationTests extends AbstractExpressionTests {
@Test
void testSimpleAccess01() {
evaluate("getPlaceOfBirth().getCity()", "SmilJan", String.class);
evaluate("getPlaceOfBirth().getCity()", "Smiljan", String.class);
}
@Test

View File

@ -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();

View File

@ -58,7 +58,7 @@ class PropertyAccessTests extends AbstractExpressionTests {
@Test
void simpleAccess02() {
evaluate("placeOfBirth.city", "SmilJan", String.class);
evaluate("placeOfBirth.city", "Smiljan", String.class);
}
@Test

View File

@ -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");
}
}

View File

@ -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");