Revise SpEL internals and documentation
This is a prerequisite for null-safe Optional support. See gh-20433
This commit is contained in:
parent
d3d951e44b
commit
2c05e991b5
|
@ -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 (`?.$`).
|
||||
|
||||
|
|
|
@ -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<TypeDescriptor> 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<TypeDescriptor> argumentTypes) {
|
||||
private @Nullable MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object target,
|
||||
@Nullable TypeDescriptor targetType, List<TypeDescriptor> argumentTypes) {
|
||||
|
||||
List<MethodResolver> 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<TypeDescriptor> argumentTypes, Object targetObject,
|
||||
private MethodExecutor findMethodExecutor(List<TypeDescriptor> 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<TypeDescriptor> argumentTypes) {
|
||||
|
||||
private final MethodExecutor methodExecutor;
|
||||
|
||||
private final @Nullable Class<?> staticClass;
|
||||
|
||||
private final @Nullable TypeDescriptor target;
|
||||
|
||||
private final List<TypeDescriptor> argumentTypes;
|
||||
|
||||
public CachedMethodExecutor(MethodExecutor methodExecutor, @Nullable Class<?> staticClass,
|
||||
@Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
|
||||
|
||||
this.methodExecutor = methodExecutor;
|
||||
this.staticClass = staticClass;
|
||||
this.target = target;
|
||||
this.argumentTypes = argumentTypes;
|
||||
}
|
||||
|
||||
public boolean isSuitable(Object value, @Nullable TypeDescriptor target, List<TypeDescriptor> 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<TypeDescriptor> 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() {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>For example: <code>{1,2,3,4,5,6,7,8,9,10}.![#isEven(#this)]</code> 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'
|
||||
|
|
|
@ -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<PropertyAccessor> 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<PropertyAccessor> 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<PropertyAccessor> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>For example, <code>{1,2,3,4,5,6,7,8,9,10}.?[#isEven(#this)]</code> 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) {
|
||||
|
|
Loading…
Reference in New Issue