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 60d406cb29..7d78953899 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 @@ -252,7 +252,6 @@ Kotlin:: <1> Use "null-safe select first" operator on potentially null `members` list ====== - The following example shows how to use the "null-safe select last" operator for collections (`?.$`). diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 41a5cb5d11..593c41e6e0 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -47,7 +47,8 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * Expression language AST node that represents a method reference. + * Expression language AST node that represents a method reference (i.e., a + * method invocation other than a simple property reference). * * @author Andy Clement * @author Juergen Hoeller @@ -101,27 +102,28 @@ public class MethodReference extends SpelNodeImpl { @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { EvaluationContext evaluationContext = state.getEvaluationContext(); - Object value = state.getActiveContextObject().getValue(); - TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor(); + TypedValue contextObject = state.getActiveContextObject(); + Object target = contextObject.getValue(); + TypeDescriptor targetType = contextObject.getTypeDescriptor(); @Nullable Object[] arguments = getArguments(state); - TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments); + TypedValue result = getValueInternal(evaluationContext, target, targetType, arguments); updateExitTypeDescriptor(); return result; } - private TypedValue getValueInternal(EvaluationContext evaluationContext, - @Nullable Object value, @Nullable TypeDescriptor targetType, @Nullable Object[] arguments) { + private TypedValue getValueInternal(EvaluationContext evaluationContext, @Nullable Object target, + @Nullable TypeDescriptor targetType, @Nullable Object[] arguments) { List argumentTypes = getArgumentTypes(arguments); - if (value == null) { + if (target == null) { throwIfNotNullSafe(argumentTypes); return TypedValue.NULL; } - MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes); + MethodExecutor executorToUse = getCachedExecutor(evaluationContext, target, targetType, argumentTypes); if (executorToUse != null) { try { - return executorToUse.execute(evaluationContext, value, arguments); + return executorToUse.execute(evaluationContext, target, arguments); } catch (AccessException ex) { // Two reasons this can occur: @@ -135,7 +137,7 @@ public class MethodReference extends SpelNodeImpl { // To determine the situation, the AccessException will contain a cause. // If the cause is an InvocationTargetException, a user exception was // thrown inside the method. Otherwise the method could not be invoked. - throwSimpleExceptionIfPossible(value, ex); + throwSimpleExceptionIfPossible(target, ex); // At this point we know it wasn't a user problem so worth a retry if a // better candidate can be found. @@ -144,18 +146,18 @@ public class MethodReference extends SpelNodeImpl { } // either there was no accessor or it no longer existed - executorToUse = findAccessorForMethod(argumentTypes, value, evaluationContext); + executorToUse = findMethodExecutor(argumentTypes, target, evaluationContext); this.cachedExecutor = new CachedMethodExecutor( - executorToUse, (value instanceof Class clazz ? clazz : null), targetType, argumentTypes); + executorToUse, (target instanceof Class clazz ? clazz : null), targetType, argumentTypes); try { - return executorToUse.execute(evaluationContext, value, arguments); + return executorToUse.execute(evaluationContext, target, arguments); } catch (AccessException ex) { - // Same unwrapping exception handling as above in above catch block - throwSimpleExceptionIfPossible(value, ex); + // Same unwrapping exception handling as in above catch block + throwSimpleExceptionIfPossible(target, ex); throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name, - value.getClass().getName(), ex.getMessage()); + target.getClass().getName(), ex.getMessage()); } } @@ -190,8 +192,8 @@ public class MethodReference extends SpelNodeImpl { return Collections.unmodifiableList(descriptors); } - private @Nullable MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value, - @Nullable TypeDescriptor target, List argumentTypes) { + private @Nullable MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object target, + @Nullable TypeDescriptor targetType, List argumentTypes) { List methodResolvers = evaluationContext.getMethodResolvers(); if (methodResolvers.size() != 1 || !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) { @@ -200,21 +202,21 @@ public class MethodReference extends SpelNodeImpl { } CachedMethodExecutor executorToCheck = this.cachedExecutor; - if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) { + if (executorToCheck != null && executorToCheck.isSuitable(target, targetType, argumentTypes)) { return executorToCheck.get(); } this.cachedExecutor = null; return null; } - private MethodExecutor findAccessorForMethod(List argumentTypes, Object targetObject, + private MethodExecutor findMethodExecutor(List argumentTypes, Object target, EvaluationContext evaluationContext) throws SpelEvaluationException { AccessException accessException = null; for (MethodResolver methodResolver : evaluationContext.getMethodResolvers()) { try { MethodExecutor methodExecutor = methodResolver.resolve( - evaluationContext, targetObject, this.name, argumentTypes); + evaluationContext, target, this.name, argumentTypes); if (methodExecutor != null) { return methodExecutor; } @@ -227,7 +229,7 @@ public class MethodReference extends SpelNodeImpl { String method = FormatHelper.formatMethodForMessage(this.name, argumentTypes); String className = FormatHelper.formatClassNameForMessage( - targetObject instanceof Class clazz ? clazz : targetObject.getClass()); + target instanceof Class clazz ? clazz : target.getClass()); if (accessException != null) { throw new SpelEvaluationException( getStartPosition(), accessException, SpelMessage.PROBLEM_LOCATING_METHOD, method, className); @@ -241,7 +243,7 @@ public class MethodReference extends SpelNodeImpl { * Decode the AccessException, throwing a lightweight evaluation exception or, * if the cause was a RuntimeException, throw the RuntimeException directly. */ - private void throwSimpleExceptionIfPossible(Object value, AccessException ex) { + private void throwSimpleExceptionIfPossible(Object target, AccessException ex) { if (ex.getCause() instanceof InvocationTargetException cause) { Throwable rootCause = cause.getCause(); if (rootCause instanceof RuntimeException runtimeException) { @@ -249,7 +251,7 @@ public class MethodReference extends SpelNodeImpl { } throw new ExpressionInvocationTargetException(getStartPosition(), "A problem occurred when trying to execute method '" + this.name + - "' on object of type [" + value.getClass().getName() + "]", rootCause); + "' on object of type [" + target.getClass().getName() + "]", rootCause); } } @@ -376,7 +378,7 @@ public class MethodReference extends SpelNodeImpl { private final EvaluationContext evaluationContext; - private final @Nullable Object value; + private final @Nullable Object target; private final @Nullable TypeDescriptor targetType; @@ -384,7 +386,7 @@ public class MethodReference extends SpelNodeImpl { public MethodValueRef(ExpressionState state, @Nullable Object[] arguments) { this.evaluationContext = state.getEvaluationContext(); - this.value = state.getActiveContextObject().getValue(); + this.target = state.getActiveContextObject().getValue(); this.targetType = state.getActiveContextObject().getTypeDescriptor(); this.arguments = arguments; } @@ -392,7 +394,7 @@ public class MethodReference extends SpelNodeImpl { @Override public TypedValue getValue() { TypedValue result = MethodReference.this.getValueInternal( - this.evaluationContext, this.value, this.targetType, this.arguments); + this.evaluationContext, this.target, this.targetType, this.arguments); updateExitTypeDescriptor(); return result; } @@ -409,32 +411,16 @@ public class MethodReference extends SpelNodeImpl { } - private static class CachedMethodExecutor { + private record CachedMethodExecutor(MethodExecutor methodExecutor, @Nullable Class staticClass, + @Nullable TypeDescriptor targetType, List argumentTypes) { - private final MethodExecutor methodExecutor; - - private final @Nullable Class staticClass; - - private final @Nullable TypeDescriptor target; - - private final List argumentTypes; - - public CachedMethodExecutor(MethodExecutor methodExecutor, @Nullable Class staticClass, - @Nullable TypeDescriptor target, List argumentTypes) { - - this.methodExecutor = methodExecutor; - this.staticClass = staticClass; - this.target = target; - this.argumentTypes = argumentTypes; - } - - public boolean isSuitable(Object value, @Nullable TypeDescriptor target, List argumentTypes) { - return ((this.staticClass == null || this.staticClass == value) && - ObjectUtils.nullSafeEquals(this.target, target) && this.argumentTypes.equals(argumentTypes)); + public boolean isSuitable(Object target, @Nullable TypeDescriptor targetType, List argumentTypes) { + return ((this.staticClass == null || this.staticClass == target) && + ObjectUtils.nullSafeEquals(this.targetType, targetType) && this.argumentTypes.equals(argumentTypes)); } public boolean hasProxyTarget() { - return (this.target != null && Proxy.isProxyClass(this.target.getType())); + return (this.targetType != null && Proxy.isProxyClass(this.targetType.getType())); } public MethodExecutor get() { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index 663e7b12be..866fdc454a 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -33,8 +33,8 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** - * Represents projection, where a given operation is performed on all elements in some - * input sequence, returning a new sequence of the same size. + * Represents projection, where a given operation is performed on all elements in + * some input sequence, returning a new sequence of the same size. * *

For example: {1,2,3,4,5,6,7,8,9,10}.![#isEven(#this)] evaluates * to {@code [n, y, n, y, n, y, n, y, n, y]}. @@ -72,8 +72,8 @@ public class Projection extends SpelNodeImpl { @Override protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { - TypedValue op = state.getActiveContextObject(); - Object operand = op.getValue(); + TypedValue contextObject = state.getActiveContextObject(); + Object operand = contextObject.getValue(); // When the input is a map, we push a Map.Entry on the stack before calling // the specified operation. Map.Entry has two properties 'key' and 'value' 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 a493198456..2f722a1865 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -179,8 +179,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl { private TypedValue readProperty(TypedValue contextObject, EvaluationContext evalContext, String name) throws EvaluationException { - Object targetObject = contextObject.getValue(); - if (targetObject == null && isNullSafe()) { + Object target = contextObject.getValue(); + if (target == null && isNullSafe()) { return TypedValue.NULL; } @@ -188,7 +188,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { if (accessorToUse != null) { if (evalContext.getPropertyAccessors().contains(accessorToUse)) { try { - return accessorToUse.read(evalContext, targetObject, name); + return accessorToUse.read(evalContext, target, name); } catch (Exception ex) { // This is OK - it may have gone stale due to a class change, @@ -199,19 +199,19 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } List accessorsToTry = - AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(target, 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 + // then ask them to read it. try { for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canRead(evalContext, targetObject, name)) { + if (accessor.canRead(evalContext, target, name)) { if (accessor instanceof ReflectivePropertyAccessor reflectivePropertyAccessor) { accessor = reflectivePropertyAccessor.createOptimalAccessor( - evalContext, targetObject, name); + evalContext, target, name); } this.cachedReadAccessor = accessor; - return accessor.read(evalContext, targetObject, name); + return accessor.read(evalContext, target, name); } } } @@ -232,8 +232,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl { TypedValue contextObject, EvaluationContext evalContext, String name, @Nullable Object newValue) throws EvaluationException { - Object targetObject = contextObject.getValue(); - if (targetObject == null) { + Object target = contextObject.getValue(); + if (target == null) { if (isNullSafe()) { return; } @@ -245,7 +245,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl { if (accessorToUse != null) { if (evalContext.getPropertyAccessors().contains(accessorToUse)) { try { - accessorToUse.write(evalContext, targetObject, name, newValue); + accessorToUse.write(evalContext, target, name, newValue); return; } catch (Exception ex) { @@ -257,12 +257,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } List accessorsToTry = - AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(target, evalContext.getPropertyAccessors()); try { for (PropertyAccessor accessor : accessorsToTry) { - if (accessor.canWrite(evalContext, targetObject, name)) { + if (accessor.canWrite(evalContext, target, name)) { this.cachedWriteAccessor = accessor; - accessor.write(evalContext, targetObject, name, newValue); + accessor.write(evalContext, target, name, newValue); return; } } @@ -273,19 +273,19 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE, name, - FormatHelper.formatClassNameForMessage(getObjectClass(targetObject))); + FormatHelper.formatClassNameForMessage(getObjectClass(target))); } public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext evalContext) throws EvaluationException { - Object targetObject = contextObject.getValue(); - if (targetObject != null) { + Object target = contextObject.getValue(); + if (target != null) { List accessorsToTry = - AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(target, evalContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { try { - if (accessor.canWrite(evalContext, targetObject, name)) { + if (accessor.canWrite(evalContext, target, name)) { return true; } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 07e7ab2aca..41106724c3 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -35,7 +35,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; /** - * Represents selection over a map or collection. + * Represents selection over a {@link Map}, {@link Iterable}, or array. * *

For example, {1,2,3,4,5,6,7,8,9,10}.?[#isEven(#this)] evaluates * to {@code [2, 4, 6, 8, 10]}. @@ -94,8 +94,8 @@ public class Selection extends SpelNodeImpl { @Override protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { - TypedValue op = state.getActiveContextObject(); - Object operand = op.getValue(); + TypedValue contextObject = state.getActiveContextObject(); + Object operand = contextObject.getValue(); SpelNodeImpl selectionCriteria = this.children[0]; if (operand instanceof Map mapdata) { @@ -151,9 +151,9 @@ public class Selection extends SpelNodeImpl { try { state.pushActiveContextObject(new TypedValue(element)); state.enterScope(); - Object val = selectionCriteria.getValueInternal(state).getValue(); - if (val instanceof Boolean b) { - if (b) { + Object criteria = selectionCriteria.getValueInternal(state).getValue(); + if (criteria instanceof Boolean match) { + if (match) { if (this.variant == FIRST) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(element), this); } @@ -184,7 +184,7 @@ public class Selection extends SpelNodeImpl { } Class elementType = null; - TypeDescriptor typeDesc = op.getTypeDescriptor(); + TypeDescriptor typeDesc = contextObject.getTypeDescriptor(); if (typeDesc != null) { TypeDescriptor elementTypeDesc = typeDesc.getElementTypeDescriptor(); if (elementTypeDesc != null) {