Polish SpEL Javadocs and internals
This commit is contained in:
parent
1080c145e3
commit
888e50175d
|
@ -80,7 +80,7 @@ public class CompositeStringExpression implements Expression {
|
|||
@Override
|
||||
@Nullable
|
||||
public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationException {
|
||||
Object value = getValue();
|
||||
String value = getValue();
|
||||
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ public class CompositeStringExpression implements Expression {
|
|||
@Override
|
||||
@Nullable
|
||||
public <T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException {
|
||||
Object value = getValue(rootObject);
|
||||
String value = getValue(rootObject);
|
||||
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType);
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ public class CompositeStringExpression implements Expression {
|
|||
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType)
|
||||
throws EvaluationException {
|
||||
|
||||
Object value = getValue(context);
|
||||
String value = getValue(context);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ public class CompositeStringExpression implements Expression {
|
|||
public <T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
|
||||
throws EvaluationException {
|
||||
|
||||
Object value = getValue(context,rootObject);
|
||||
String value = getValue(context,rootObject);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ public class LiteralExpression implements Expression {
|
|||
@Override
|
||||
@Nullable
|
||||
public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationException {
|
||||
Object value = getValue();
|
||||
String value = getValue();
|
||||
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class LiteralExpression implements Expression {
|
|||
@Override
|
||||
@Nullable
|
||||
public <T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException {
|
||||
Object value = getValue(rootObject);
|
||||
String value = getValue(rootObject);
|
||||
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType);
|
||||
}
|
||||
|
||||
|
@ -87,10 +87,8 @@ public class LiteralExpression implements Expression {
|
|||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType)
|
||||
throws EvaluationException {
|
||||
|
||||
Object value = getValue(context);
|
||||
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType) throws EvaluationException {
|
||||
String value = getValue(context);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
|
@ -104,7 +102,7 @@ public class LiteralExpression implements Expression {
|
|||
public <T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
|
||||
throws EvaluationException {
|
||||
|
||||
Object value = getValue(context, rootObject);
|
||||
String value = getValue(context, rootObject);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -21,7 +21,6 @@ import java.util.StringJoiner;
|
|||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Utility methods (formatters, etc) used during parsing and evaluation.
|
||||
|
@ -51,10 +50,9 @@ abstract class FormatHelper {
|
|||
* <p>A String array will have the formatted name "java.lang.String[]".
|
||||
* @param clazz the Class whose name is to be formatted
|
||||
* @return a formatted String suitable for message inclusion
|
||||
* @see ClassUtils#getQualifiedName(Class)
|
||||
*/
|
||||
static String formatClassNameForMessage(@Nullable Class<?> clazz) {
|
||||
return (clazz != null ? ClassUtils.getQualifiedName(clazz) : "null");
|
||||
return (clazz != null ? clazz.getTypeName() : "null");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -39,16 +39,21 @@ import org.springframework.util.ClassUtils;
|
|||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A function reference is of the form "#someFunction(a,b,c)". Functions may be defined
|
||||
* in the context prior to the expression being evaluated. Functions may also be static
|
||||
* Java methods, registered in the context prior to invocation of the expression.
|
||||
* A function reference is of the form "#someFunction(a,b,c)".
|
||||
*
|
||||
* <p>Functions are very simplistic. The arguments are not part of the definition
|
||||
* (right now), so the names must be unique.
|
||||
* <p>Functions can be either a {@link Method} (for static Java methods) or a
|
||||
* {@link MethodHandle} and must be registered in the context prior to evaluation
|
||||
* of the expression. See the {@code registerFunction()} methods in
|
||||
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
|
||||
* for details.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction(String, Method)
|
||||
* @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction(String, MethodHandle)
|
||||
*/
|
||||
public class FunctionReference extends SpelNodeImpl {
|
||||
|
||||
|
@ -72,39 +77,44 @@ public class FunctionReference extends SpelNodeImpl {
|
|||
if (value == TypedValue.NULL) {
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name);
|
||||
}
|
||||
Object resolvedValue = value.getValue();
|
||||
if (resolvedValue instanceof MethodHandle methodHandle) {
|
||||
Object function = value.getValue();
|
||||
|
||||
// Static Java method registered via a Method.
|
||||
// Note: "javaMethod" cannot be named "method" due to a bug in Checkstyle.
|
||||
if (function instanceof Method javaMethod) {
|
||||
try {
|
||||
return executeFunctionBoundMethodHandle(state, methodHandle);
|
||||
return executeFunctionViaMethod(state, javaMethod);
|
||||
}
|
||||
catch (SpelEvaluationException ex) {
|
||||
ex.setPosition(getStartPosition());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
if (!(resolvedValue instanceof Method function)) {
|
||||
// Possibly a static Java method registered as a function
|
||||
throw new SpelEvaluationException(
|
||||
SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
|
||||
|
||||
// Function registered via a MethodHandle.
|
||||
if (function instanceof MethodHandle methodHandle) {
|
||||
try {
|
||||
return executeFunctionViaMethodHandle(state, methodHandle);
|
||||
}
|
||||
catch (SpelEvaluationException ex) {
|
||||
ex.setPosition(getStartPosition());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return executeFunctionJLRMethod(state, function);
|
||||
}
|
||||
catch (SpelEvaluationException ex) {
|
||||
ex.setPosition(getStartPosition());
|
||||
throw ex;
|
||||
}
|
||||
// Neither a Method nor a MethodHandle?
|
||||
throw new SpelEvaluationException(
|
||||
SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function represented as a {@code java.lang.reflect.Method}.
|
||||
* Execute a function represented as a {@link Method}.
|
||||
* @param state the expression evaluation state
|
||||
* @param method the method to invoke
|
||||
* @return the return value of the invoked Java method
|
||||
* @throws EvaluationException if there is any problem invoking the method
|
||||
*/
|
||||
private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
|
||||
private TypedValue executeFunctionViaMethod(ExpressionState state, Method method) throws EvaluationException {
|
||||
Object[] functionArgs = getArguments(state);
|
||||
|
||||
if (!method.isVarArgs()) {
|
||||
|
@ -151,17 +161,17 @@ public class FunctionReference extends SpelNodeImpl {
|
|||
}
|
||||
|
||||
/**
|
||||
* Execute a function represented as {@code java.lang.invoke.MethodHandle}.
|
||||
* Method types that take no arguments (fully bound handles or static methods
|
||||
* with no parameters) can use {@code #invoke()} which is the most efficient.
|
||||
* Otherwise, {@code #invokeWithArguments)} is used.
|
||||
* Execute a function represented as {@link MethodHandle}.
|
||||
* <p>Method types that take no arguments (fully bound handles or static methods
|
||||
* with no parameters) can use {@link MethodHandle#invoke()} which is the most
|
||||
* efficient. Otherwise, {@link MethodHandle#invokeWithArguments()} is used.
|
||||
* @param state the expression evaluation state
|
||||
* @param methodHandle the method to invoke
|
||||
* @param methodHandle the method handle to invoke
|
||||
* @return the return value of the invoked Java method
|
||||
* @throws EvaluationException if there is any problem invoking the method
|
||||
* @since 6.1
|
||||
*/
|
||||
private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, MethodHandle methodHandle) throws EvaluationException {
|
||||
private TypedValue executeFunctionViaMethodHandle(ExpressionState state, MethodHandle methodHandle) throws EvaluationException {
|
||||
Object[] functionArgs = getArguments(state);
|
||||
MethodType declaredParams = methodHandle.type();
|
||||
int spelParamCount = functionArgs.length;
|
||||
|
@ -169,17 +179,15 @@ public class FunctionReference extends SpelNodeImpl {
|
|||
|
||||
boolean isSuspectedVarargs = declaredParams.lastParameterType().isArray();
|
||||
|
||||
if (spelParamCount < declaredParamCount || (spelParamCount > declaredParamCount
|
||||
&& !isSuspectedVarargs)) {
|
||||
//incorrect number, including more arguments and not a vararg
|
||||
if (spelParamCount < declaredParamCount || (spelParamCount > declaredParamCount && !isSuspectedVarargs)) {
|
||||
// incorrect number, including more arguments and not a vararg
|
||||
// perhaps a subset of arguments was provided but the MethodHandle wasn't bound?
|
||||
throw new SpelEvaluationException(SpelMessage.INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
|
||||
functionArgs.length, declaredParamCount);
|
||||
//perhaps a subset of arguments was provided but the MethodHandle wasn't bound?
|
||||
}
|
||||
|
||||
// simplest case: the MethodHandle is fully bound or represents a static method with no params:
|
||||
if (declaredParamCount == 0) {
|
||||
//note we consider MethodHandles not compilable
|
||||
try {
|
||||
return new TypedValue(methodHandle.invoke());
|
||||
}
|
||||
|
@ -188,6 +196,7 @@ public class FunctionReference extends SpelNodeImpl {
|
|||
this.name, ex.getMessage());
|
||||
}
|
||||
finally {
|
||||
// Note: we consider MethodHandles not compilable
|
||||
this.exitTypeDescriptor = null;
|
||||
this.method = null;
|
||||
}
|
||||
|
@ -202,12 +211,11 @@ public class FunctionReference extends SpelNodeImpl {
|
|||
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
|
||||
|
||||
if (isSuspectedVarargs && declaredParamCount == 1) {
|
||||
//we only repack the varargs if it is the ONLY argument
|
||||
// we only repack the varargs if it is the ONLY argument
|
||||
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
|
||||
methodHandle.type().parameterArray(), functionArgs);
|
||||
}
|
||||
|
||||
//note we consider MethodHandles not compilable
|
||||
try {
|
||||
return new TypedValue(methodHandle.invokeWithArguments(functionArgs));
|
||||
}
|
||||
|
@ -216,6 +224,7 @@ public class FunctionReference extends SpelNodeImpl {
|
|||
this.name, ex.getMessage());
|
||||
}
|
||||
finally {
|
||||
// Note: we consider MethodHandles not compilable
|
||||
this.exitTypeDescriptor = null;
|
||||
this.method = null;
|
||||
}
|
||||
|
|
|
@ -180,12 +180,12 @@ public class Indexer extends SpelNodeImpl {
|
|||
}
|
||||
else {
|
||||
this.indexedType = IndexedType.STRING;
|
||||
return new StringIndexingLValue((String) target, idx, targetDescriptor);
|
||||
return new StringIndexingValueRef((String) target, idx, targetDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// Try and treat the index value as a property of the context object
|
||||
// TODO: could call the conversion service to convert the value to a String
|
||||
// TODO Could call the conversion service to convert the value to a String
|
||||
TypeDescriptor valueType = indexValue.getTypeDescriptor();
|
||||
if (valueType != null && String.class == valueType.getType()) {
|
||||
this.indexedType = IndexedType.OBJECT;
|
||||
|
@ -226,41 +226,42 @@ public class Indexer extends SpelNodeImpl {
|
|||
}
|
||||
|
||||
if (this.indexedType == IndexedType.ARRAY) {
|
||||
int insn;
|
||||
if ("D".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[D");
|
||||
insn = DALOAD;
|
||||
}
|
||||
else if ("F".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[F");
|
||||
insn = FALOAD;
|
||||
}
|
||||
else if ("J".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[J");
|
||||
insn = LALOAD;
|
||||
}
|
||||
else if ("I".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[I");
|
||||
insn = IALOAD;
|
||||
}
|
||||
else if ("S".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[S");
|
||||
insn = SALOAD;
|
||||
}
|
||||
else if ("B".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[B");
|
||||
insn = BALOAD;
|
||||
}
|
||||
else if ("C".equals(this.exitTypeDescriptor)) {
|
||||
mv.visitTypeInsn(CHECKCAST, "[C");
|
||||
insn = CALOAD;
|
||||
}
|
||||
else {
|
||||
mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor +
|
||||
(CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";"));
|
||||
//depthPlusOne(exitTypeDescriptor)+"Ljava/lang/Object;");
|
||||
insn = AALOAD;
|
||||
}
|
||||
int insn = switch (this.exitTypeDescriptor) {
|
||||
case "D" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[D");
|
||||
yield DALOAD;
|
||||
}
|
||||
case "F" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[F");
|
||||
yield FALOAD;
|
||||
}
|
||||
case "J" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[J");
|
||||
yield LALOAD;
|
||||
}
|
||||
case "I" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[I");
|
||||
yield IALOAD;
|
||||
}
|
||||
case "S" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[S");
|
||||
yield SALOAD;
|
||||
}
|
||||
case "B" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[B");
|
||||
yield BALOAD;
|
||||
}
|
||||
case "C" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[C");
|
||||
yield CALOAD;
|
||||
}
|
||||
default -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor +
|
||||
(CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";"));
|
||||
yield AALOAD;
|
||||
}
|
||||
};
|
||||
|
||||
SpelNodeImpl index = this.children[0];
|
||||
cf.enterCompilationScope();
|
||||
index.generateCode(mv, cf);
|
||||
|
@ -325,6 +326,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
|
||||
@Override
|
||||
public String toStringAST() {
|
||||
// TODO Since we do not support multidimensional arrays, we should be able to return: "[" + getChild(0).toStringAST() + "]"
|
||||
StringJoiner sj = new StringJoiner(",", "[", "]");
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
sj.add(getChild(i).toStringAST());
|
||||
|
@ -744,7 +746,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
}
|
||||
|
||||
|
||||
private class StringIndexingLValue implements ValueRef {
|
||||
private class StringIndexingValueRef implements ValueRef {
|
||||
|
||||
private final String target;
|
||||
|
||||
|
@ -752,7 +754,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
|
||||
private final TypeDescriptor typeDescriptor;
|
||||
|
||||
public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) {
|
||||
public StringIndexingValueRef(String target, int index, TypeDescriptor typeDescriptor) {
|
||||
this.target = target;
|
||||
this.index = index;
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
|
|
|
@ -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.
|
||||
|
@ -111,8 +111,7 @@ public class InlineList extends SpelNodeImpl {
|
|||
public String toStringAST() {
|
||||
StringJoiner sj = new StringJoiner(",", "{", "}");
|
||||
// String ast matches input string, not the 'toString()' of the resultant collection, which would use []
|
||||
int count = getChildCount();
|
||||
for (int c = 0; c < count; c++) {
|
||||
for (int c = 0; c < getChildCount(); c++) {
|
||||
sj.add(getChild(c).toStringAST());
|
||||
}
|
||||
return sj.toString();
|
||||
|
|
|
@ -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.
|
||||
|
@ -143,8 +143,7 @@ public class InlineMap extends SpelNodeImpl {
|
|||
@Override
|
||||
public String toStringAST() {
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
int count = getChildCount();
|
||||
for (int c = 0; c < count; c++) {
|
||||
for (int c = 0; c < getChildCount(); c++) {
|
||||
if (c > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
|
@ -60,10 +60,7 @@ public class Projection extends SpelNodeImpl {
|
|||
@Override
|
||||
protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||
TypedValue op = state.getActiveContextObject();
|
||||
|
||||
Object operand = op.getValue();
|
||||
boolean operandIsArray = ObjectUtils.isArray(operand);
|
||||
// TypeDescriptor operandTypeDescriptor = op.getTypeDescriptor();
|
||||
|
||||
// When the input is a map, we push a special context object on the stack
|
||||
// before calling the specified operation. This special context object
|
||||
|
@ -86,6 +83,7 @@ public class Projection extends SpelNodeImpl {
|
|||
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); // TODO unable to build correct type descriptor
|
||||
}
|
||||
|
||||
boolean operandIsArray = ObjectUtils.isArray(operand);
|
||||
if (operand instanceof Iterable || operandIsArray) {
|
||||
Iterable<?> data = (operand instanceof Iterable<?> iterable ?
|
||||
iterable : Arrays.asList(ObjectUtils.toObjectArray(operand)));
|
||||
|
|
|
@ -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.
|
||||
|
@ -90,7 +90,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||
|
||||
@Override
|
||||
public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||
return new AccessorLValue(this, state.getActiveContextObject(), state.getEvaluationContext(),
|
||||
return new AccessorValueRef(this, state.getActiveContextObject(), state.getEvaluationContext(),
|
||||
state.getConfiguration().isAutoGrowNullReferences());
|
||||
}
|
||||
|
||||
|
@ -391,7 +391,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||
}
|
||||
|
||||
|
||||
private static class AccessorLValue implements ValueRef {
|
||||
private static class AccessorValueRef implements ValueRef {
|
||||
|
||||
private final PropertyOrFieldReference ref;
|
||||
|
||||
|
@ -401,7 +401,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||
|
||||
private final boolean autoGrowNullReferences;
|
||||
|
||||
public AccessorLValue(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject,
|
||||
public AccessorValueRef(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject,
|
||||
EvaluationContext evalContext, boolean autoGrowNullReferences) {
|
||||
|
||||
this.ref = propertyOrFieldReference;
|
||||
|
|
|
@ -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.
|
||||
|
@ -19,39 +19,47 @@ package org.springframework.expression.spel.standard;
|
|||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Holder for a kind of token, the associated data and its position in the input data
|
||||
* stream (start/end).
|
||||
* Holder for a kind of token, the associated data, and its position in the input
|
||||
* data stream (start/end).
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 3.0
|
||||
*/
|
||||
class Token {
|
||||
|
||||
TokenKind kind;
|
||||
final TokenKind kind;
|
||||
|
||||
@Nullable
|
||||
String data;
|
||||
final String data;
|
||||
|
||||
int startPos; // index of first character
|
||||
final int startPos;
|
||||
|
||||
int endPos; // index of char after the last character
|
||||
final int endPos;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for use when there is no particular data for the token
|
||||
* (e.g. TRUE or '+')
|
||||
* @param startPos the exact start
|
||||
* @param endPos the index to the last character
|
||||
* (e.g. TRUE or '+').
|
||||
* @param tokenKind the kind of token
|
||||
* @param startPos the exact start position
|
||||
* @param endPos the index of the last character
|
||||
*/
|
||||
Token(TokenKind tokenKind, int startPos, int endPos) {
|
||||
this.kind = tokenKind;
|
||||
this.startPos = startPos;
|
||||
this.endPos = endPos;
|
||||
this(tokenKind, null, startPos, endPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for use when there is data for the token.
|
||||
* @param tokenKind the kind of token
|
||||
* @param tokenData the data for the token
|
||||
* @param startPos the exact start position
|
||||
* @param endPos the index of the last character
|
||||
*/
|
||||
Token(TokenKind tokenKind, char[] tokenData, int startPos, int endPos) {
|
||||
this(tokenKind, startPos, endPos);
|
||||
this.data = new String(tokenData);
|
||||
this.kind = tokenKind;
|
||||
this.data = (tokenData != null ? new String(tokenData) : null);
|
||||
this.startPos = startPos;
|
||||
this.endPos = endPos;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -279,7 +279,7 @@ public class StandardEvaluationContext implements EvaluationContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* Register the specified Method as a SpEL function.
|
||||
* Register the specified {@link Method} as a SpEL function.
|
||||
* <p>Note: Function names share a namespace with the variables in this
|
||||
* evaluation context, as populated by {@link #setVariable(String, Object)}.
|
||||
* Make sure that specified function names and variable names do not overlap.
|
||||
|
@ -292,7 +292,7 @@ public class StandardEvaluationContext implements EvaluationContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* Register the specified MethodHandle as a SpEL function.
|
||||
* Register the specified {@link MethodHandle} as a SpEL function.
|
||||
* <p>Note: Function names share a namespace with the variables in this
|
||||
* evaluation context, as populated by {@link #setVariable(String, Object)}.
|
||||
* Make sure that specified function names and variable names do not overlap.
|
||||
|
|
|
@ -112,6 +112,24 @@ class ParsingTests {
|
|||
parseCheck("#var1='value1'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexing() {
|
||||
parseCheck("#var[2]");
|
||||
parseCheck("person['name']");
|
||||
parseCheck("person[name]");
|
||||
parseCheck("array[2]");
|
||||
parseCheck("array[2][3]");
|
||||
parseCheck("func()[2]");
|
||||
parseCheck("#func()[2]");
|
||||
parseCheck("'abc'[2]");
|
||||
parseCheck("\"abc\"[2]", "'abc'[2]");
|
||||
parseCheck("{1,2,3}[2]");
|
||||
parseCheck("{'k':'v'}['k']");
|
||||
parseCheck("{'k':'v'}[k]");
|
||||
parseCheck("{'k1':'v1','k2':'v2'}['k2']");
|
||||
parseCheck("{'k1':'v1','k2':'v2'}[k2]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void projection() {
|
||||
parseCheck("{1,2,3,4,5,6,7,8,9,10}.![#isEven()]");
|
||||
|
|
|
@ -40,15 +40,22 @@ import org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMat
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.CLOSE;
|
||||
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.EXACT;
|
||||
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.REQUIRES_CONVERSION;
|
||||
|
||||
/**
|
||||
* Tests for reflection helper code.
|
||||
* Tests for {@link ReflectionHelper}, {@link SpelUtilities}, {@link TypedValue},
|
||||
* {@link ReflectivePropertyAccessor}, ...
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class ReflectionHelperTests extends AbstractExpressionTests {
|
||||
|
||||
private final StandardTypeConverter tc = new StandardTypeConverter();
|
||||
|
||||
|
||||
@Test
|
||||
void utilities() throws ParseException {
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("3+4+5+6+7-2");
|
||||
|
@ -100,44 +107,38 @@ class ReflectionHelperTests extends AbstractExpressionTests {
|
|||
|
||||
@Test
|
||||
void reflectionHelperCompareArguments_ExactMatching() {
|
||||
StandardTypeConverter tc = new StandardTypeConverter();
|
||||
|
||||
// Calling foo(String) with (String) is exact match
|
||||
checkMatch(new Class<?>[] {String.class}, new Class<?>[] {String.class}, tc, ReflectionHelper.ArgumentsMatchKind.EXACT);
|
||||
checkMatch(new Class<?>[] {String.class}, new Class<?>[] {String.class}, tc, EXACT);
|
||||
|
||||
// Calling foo(String,Integer) with (String,Integer) is exact match
|
||||
checkMatch(new Class<?>[] {String.class, Integer.class}, new Class<?>[] {String.class, Integer.class}, tc, ArgumentsMatchKind.EXACT);
|
||||
checkMatch(new Class<?>[] {String.class, Integer.class}, new Class<?>[] {String.class, Integer.class}, tc, EXACT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reflectionHelperCompareArguments_CloseMatching() {
|
||||
StandardTypeConverter tc = new StandardTypeConverter();
|
||||
|
||||
// Calling foo(List) with (ArrayList) is close match (no conversion required)
|
||||
checkMatch(new Class<?>[] {ArrayList.class}, new Class<?>[] {List.class}, tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatch(new Class<?>[] {ArrayList.class}, new Class<?>[] {List.class}, tc, CLOSE);
|
||||
|
||||
// Passing (Sub,String) on call to foo(Super,String) is close match
|
||||
checkMatch(new Class<?>[] {Sub.class, String.class}, new Class<?>[] {Super.class, String.class}, tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatch(new Class<?>[] {Sub.class, String.class}, new Class<?>[] {Super.class, String.class}, tc, CLOSE);
|
||||
|
||||
// Passing (String,Sub) on call to foo(String,Super) is close match
|
||||
checkMatch(new Class<?>[] {String.class, Sub.class}, new Class<?>[] {String.class, Super.class}, tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatch(new Class<?>[] {String.class, Sub.class}, new Class<?>[] {String.class, Super.class}, tc, CLOSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reflectionHelperCompareArguments_RequiresConversionMatching() {
|
||||
StandardTypeConverter tc = new StandardTypeConverter();
|
||||
|
||||
void reflectionHelperCompareArguments_CloseMatching_WithAutoBoxing() {
|
||||
// Calling foo(String,int) with (String,Integer) requires boxing conversion of argument one
|
||||
checkMatch(new Class<?>[] {String.class, int.class}, new Class<?>[] {String.class,Integer.class},tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatch(new Class<?>[] {String.class, int.class}, new Class<?>[] {String.class, Integer.class},tc, CLOSE);
|
||||
|
||||
// Passing (int,String) on call to foo(Integer,String) requires boxing conversion of argument zero
|
||||
checkMatch(new Class<?>[] {int.class, String.class}, new Class<?>[] {Integer.class, String.class},tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatch(new Class<?>[] {int.class, String.class}, new Class<?>[] {Integer.class, String.class},tc, CLOSE);
|
||||
|
||||
// Passing (int,Sub) on call to foo(Integer,Super) requires boxing conversion of argument zero
|
||||
checkMatch(new Class<?>[] {int.class, Sub.class}, new Class<?>[] {Integer.class, Super.class}, tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatch(new Class<?>[] {int.class, Sub.class}, new Class<?>[] {Integer.class, Super.class}, tc, CLOSE);
|
||||
|
||||
// Passing (int,Sub,boolean) on call to foo(Integer,Super,Boolean) requires boxing conversion of arguments zero and two
|
||||
// TODO checkMatch(new Class<?>[] {int.class, Sub.class, boolean.class}, new Class<?>[] {Integer.class, Super.class, Boolean.class}, tc, ArgsMatchKind.REQUIRES_CONVERSION);
|
||||
checkMatch(new Class<?>[] {int.class, Sub.class, boolean.class}, new Class<?>[] {Integer.class, Super.class, Boolean.class}, tc, CLOSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -149,59 +150,56 @@ class ReflectionHelperTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void reflectionHelperCompareArguments_Varargs_ExactMatching() {
|
||||
StandardTypeConverter tc = new StandardTypeConverter();
|
||||
|
||||
void reflectionHelperCompareArguments_Varargs() {
|
||||
// Passing (String[]) on call to (String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {String[].class}, new Class<?>[] {String[].class}, tc, ArgumentsMatchKind.EXACT);
|
||||
checkMatchVarargs(new Class<?>[] {String[].class}, new Class<?>[] {String[].class}, tc, EXACT);
|
||||
|
||||
// Passing (Integer, String[]) on call to (Integer, String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {Integer.class, String[].class}, new Class<?>[] {Integer.class, String[].class}, tc, ArgumentsMatchKind.EXACT);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, String[].class}, new Class<?>[] {Integer.class, String[].class}, tc, EXACT);
|
||||
|
||||
// Passing (String, Integer, String[]) on call to (String, String, String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {String.class, Integer.class, String[].class}, new Class<?>[] {String.class,Integer.class, String[].class}, tc, ArgumentsMatchKind.EXACT);
|
||||
checkMatchVarargs(new Class<?>[] {String.class, Integer.class, String[].class}, new Class<?>[] {String.class,Integer.class, String[].class}, tc, EXACT);
|
||||
|
||||
// Passing (Sub, String[]) on call to (Super, String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {Sub.class, String[].class}, new Class<?>[] {Super.class,String[].class}, tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatchVarargs(new Class<?>[] {Sub.class, String[].class}, new Class<?>[] {Super.class,String[].class}, tc, CLOSE);
|
||||
|
||||
// Passing (Integer, String[]) on call to (String, String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {Integer.class, String[].class}, new Class<?>[] {String.class, String[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, String[].class}, new Class<?>[] {String.class, String[].class}, tc, REQUIRES_CONVERSION);
|
||||
|
||||
// Passing (Integer, Sub, String[]) on call to (String, Super, String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {Integer.class, Sub.class, String[].class}, new Class<?>[] {String.class, Super.class, String[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, Sub.class, String[].class}, new Class<?>[] {String.class, Super.class, String[].class}, tc, REQUIRES_CONVERSION);
|
||||
|
||||
// Passing (String) on call to (String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {String.class}, new Class<?>[] {String[].class}, tc, ArgumentsMatchKind.EXACT);
|
||||
checkMatchVarargs(new Class<?>[] {String.class}, new Class<?>[] {String[].class}, tc, EXACT);
|
||||
|
||||
// Passing (Integer,String) on call to (Integer,String[]) is exact match
|
||||
checkMatch2(new Class<?>[] {Integer.class, String.class}, new Class<?>[] {Integer.class, String[].class}, tc, ArgumentsMatchKind.EXACT);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, String.class}, new Class<?>[] {Integer.class, String[].class}, tc, EXACT);
|
||||
|
||||
// Passing (String) on call to (Integer[]) is conversion match (String to Integer)
|
||||
checkMatch2(new Class<?>[] {String.class}, new Class<?>[] {Integer[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION);
|
||||
checkMatchVarargs(new Class<?>[] {String.class}, new Class<?>[] {Integer[].class}, tc, REQUIRES_CONVERSION);
|
||||
|
||||
// Passing (Sub) on call to (Super[]) is close match
|
||||
checkMatch2(new Class<?>[] {Sub.class}, new Class<?>[] {Super[].class}, tc, ArgumentsMatchKind.CLOSE);
|
||||
checkMatchVarargs(new Class<?>[] {Sub.class}, new Class<?>[] {Super[].class}, tc, CLOSE);
|
||||
|
||||
// Passing (Super) on call to (Sub[]) is not a match
|
||||
checkMatch2(new Class<?>[] {Super.class}, new Class<?>[] {Sub[].class}, tc, null);
|
||||
checkMatchVarargs(new Class<?>[] {Super.class}, new Class<?>[] {Sub[].class}, tc, null);
|
||||
|
||||
checkMatch2(new Class<?>[] {Unconvertable.class, String.class}, new Class<?>[] {Sub.class, Super[].class}, tc, null);
|
||||
checkMatchVarargs(new Class<?>[] {Unconvertable.class, String.class}, new Class<?>[] {Sub.class, Super[].class}, tc, null);
|
||||
|
||||
checkMatch2(new Class<?>[] {Integer.class, Integer.class, String.class}, new Class<?>[] {String.class, String.class, Super[].class}, tc, null);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, Integer.class, String.class}, new Class<?>[] {String.class, String.class, Super[].class}, tc, null);
|
||||
|
||||
checkMatch2(new Class<?>[] {Unconvertable.class, String.class}, new Class<?>[] {Sub.class, Super[].class}, tc, null);
|
||||
checkMatchVarargs(new Class<?>[] {Unconvertable.class, String.class}, new Class<?>[] {Sub.class, Super[].class}, tc, null);
|
||||
|
||||
checkMatch2(new Class<?>[] {Integer.class, Integer.class, String.class}, new Class<?>[] {String.class, String.class, Super[].class}, tc, null);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, Integer.class, String.class}, new Class<?>[] {String.class, String.class, Super[].class}, tc, null);
|
||||
|
||||
checkMatch2(new Class<?>[] {Integer.class, Integer.class, Sub.class}, new Class<?>[] {String.class, String.class, Super[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, Integer.class, Sub.class}, new Class<?>[] {String.class, String.class, Super[].class}, tc, REQUIRES_CONVERSION);
|
||||
|
||||
checkMatch2(new Class<?>[] {Integer.class, Integer.class, Integer.class}, new Class<?>[] {Integer.class, String[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION);
|
||||
checkMatchVarargs(new Class<?>[] {Integer.class, Integer.class, Integer.class}, new Class<?>[] {Integer.class, String[].class}, tc, REQUIRES_CONVERSION);
|
||||
// what happens on (Integer,String) passed to (Integer[]) ?
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertArguments() throws Exception {
|
||||
StandardTypeConverter tc = new StandardTypeConverter();
|
||||
Method oneArg = TestInterface.class.getMethod("oneArg", String.class);
|
||||
Method twoArg = TestInterface.class.getMethod("twoArg", String.class, String[].class);
|
||||
|
||||
|
@ -227,8 +225,7 @@ class ReflectionHelperTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void convertArguments2() throws Exception {
|
||||
StandardTypeConverter tc = new StandardTypeConverter();
|
||||
void convertAllArguments() throws Exception {
|
||||
Method oneArg = TestInterface.class.getMethod("oneArg", String.class);
|
||||
Method twoArg = TestInterface.class.getMethod("twoArg", String.class, String[].class);
|
||||
|
||||
|
@ -254,7 +251,7 @@ class ReflectionHelperTests extends AbstractExpressionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void setupArguments() {
|
||||
void setupArgumentsForVarargsInvocation() {
|
||||
Object[] newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
|
||||
new Class<?>[] {String[].class}, "a", "b", "c");
|
||||
|
||||
|
@ -402,21 +399,21 @@ class ReflectionHelperTests extends AbstractExpressionTests {
|
|||
assertThat(matchInfo).as("Should not be a null match").isNotNull();
|
||||
}
|
||||
|
||||
if (expectedMatchKind == ArgumentsMatchKind.EXACT) {
|
||||
if (expectedMatchKind == EXACT) {
|
||||
assertThat(matchInfo.isExactMatch()).isTrue();
|
||||
}
|
||||
else if (expectedMatchKind == ArgumentsMatchKind.CLOSE) {
|
||||
else if (expectedMatchKind == CLOSE) {
|
||||
assertThat(matchInfo.isCloseMatch()).isTrue();
|
||||
}
|
||||
else if (expectedMatchKind == ArgumentsMatchKind.REQUIRES_CONVERSION) {
|
||||
else if (expectedMatchKind == REQUIRES_CONVERSION) {
|
||||
assertThat(matchInfo.isMatchRequiringConversion()).as("expected to be a match requiring conversion, but was " + matchInfo).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate the match returned from a compareArguments call.
|
||||
* Used to validate the match returned from a compareArgumentsVarargs call.
|
||||
*/
|
||||
private static void checkMatch2(Class<?>[] inputTypes, Class<?>[] expectedTypes,
|
||||
private static void checkMatchVarargs(Class<?>[] inputTypes, Class<?>[] expectedTypes,
|
||||
StandardTypeConverter typeConverter, ArgumentsMatchKind expectedMatchKind) {
|
||||
|
||||
ReflectionHelper.ArgumentsMatchInfo matchInfo =
|
||||
|
@ -438,14 +435,10 @@ class ReflectionHelperTests extends AbstractExpressionTests {
|
|||
private static void checkArguments(Object[] args, Object... expected) {
|
||||
assertThat(args).hasSize(expected.length);
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
checkArgument(expected[i], args[i]);
|
||||
assertThat(args[i]).isEqualTo(expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkArgument(Object expected, Object actual) {
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private static List<TypeDescriptor> typeDescriptors(Class<?>... types) {
|
||||
return Arrays.stream(types).map(TypeDescriptor::valueOf).toList();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue