Skip to content

Commit

Permalink
Introduce null-safe index operator in SpEL
Browse files Browse the repository at this point in the history
  • Loading branch information
Trympyrym authored and sbrannen committed Mar 23, 2024
1 parent 2a1abb5 commit 9f4d46f
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ private enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT}
private PropertyAccessor cachedWriteAccessor;


/**
* Create an {@code Indexer} with the given start position, end position, and
* index expression.
*/
public Indexer(int startPos, int endPos, SpelNodeImpl indexExpression) {
super(startPos, endPos, indexExpression);
private final boolean nullSafe;


public Indexer(boolean nullSafe, int startPos, int endPos, SpelNodeImpl expr) {
super(startPos, endPos, expr);
this.nullSafe = nullSafe;
}


Expand Down Expand Up @@ -161,6 +161,9 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException

// Raise a proper exception in case of a null target
if (target == null) {
if (this.nullSafe) {
return ValueRef.NullValueRef.INSTANCE;
}
throw new SpelEvaluationException(getStartPosition(), SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ private SpelNodeImpl eatNode() {
@Nullable
private SpelNodeImpl eatNonDottedNode() {
if (peekToken(TokenKind.LSQUARE)) {
if (maybeEatIndexer()) {
if (maybeEatIndexer(false)) {
return pop();
}
}
Expand All @@ -419,7 +419,8 @@ private SpelNodeImpl eatDottedNode() {
Token t = takeToken(); // it was a '.' or a '?.'
boolean nullSafeNavigation = (t.kind == TokenKind.SAFE_NAVI);
if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() ||
maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) {
maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation) ||
maybeEatIndexer(nullSafeNavigation)) {
return pop();
}
if (peekToken() == null) {
Expand Down Expand Up @@ -537,7 +538,8 @@ else if (maybeEatTypeReference() || maybeEatNullReference() || maybeEatConstruct
else if (maybeEatBeanReference()) {
return pop();
}
else if (maybeEatProjection(false) || maybeEatSelection(false) || maybeEatIndexer()) {
else if (maybeEatProjection(false) || maybeEatSelection(false) ||
maybeEatIndexer(false)) {
return pop();
}
else if (maybeEatInlineListOrMap()) {
Expand Down Expand Up @@ -699,7 +701,7 @@ else if (peekToken(TokenKind.COLON, true)) { // map!
return true;
}

private boolean maybeEatIndexer() {
private boolean maybeEatIndexer(boolean nullSafeNavigation) {
Token t = peekToken();
if (t == null || !peekToken(TokenKind.LSQUARE, true)) {
return false;
Expand All @@ -709,7 +711,7 @@ private boolean maybeEatIndexer() {
throw internalException(t.startPos, SpelMessage.MISSING_SELECTION_EXPRESSION);
}
eatToken(TokenKind.RSQUARE);
this.constructedNodes.push(new Indexer(t.startPos, t.endPos, expr));
this.constructedNodes.push(new Indexer(nullSafeNavigation, t.startPos, t.endPos, expr));
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,20 @@ void listOfMaps() {
assertThat(expression.getValue(this, String.class)).isEqualTo("apple");
}

@Test
void nullSafeIndex() {
ContextWithNullCollections testContext = new ContextWithNullCollections();
StandardEvaluationContext context = new StandardEvaluationContext(testContext);
Expression expr = new SpelExpressionParser().parseRaw("nullList?.[4]");
assertThat(expr.getValue(context)).isNull();

expr = new SpelExpressionParser().parseRaw("nullArray?.[4]");
assertThat(expr.getValue(context)).isNull();

expr = new SpelExpressionParser().parseRaw("nullMap:?.[4]");
assertThat(expr.getValue(context)).isNull();
}


@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -436,4 +450,11 @@ public Class<?>[] getSpecificTargetClasses() {

}


static class ContextWithNullCollections {
public List nullList = null;
public String[] nullArray = null;
public Map nullMap = null;
}

}

0 comments on commit 9f4d46f

Please sign in to comment.