Polish SpEL Javadocs and internals

This commit is contained in:
Sam Brannen 2024-02-10 15:03:02 +01:00
parent 1080c145e3
commit 888e50175d
13 changed files with 194 additions and 172 deletions

View File

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

View File

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

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

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.
@ -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;
}

View File

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

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

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.
@ -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(',');
}

View File

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

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

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.
@ -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;
}

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

View File

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

View File

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