PropertyAccessorFactoryLambda.java
package com.alibaba.fastjson2.introspect;
import com.alibaba.fastjson2.function.*;
import com.alibaba.fastjson2.util.JDKUtils;
import java.lang.invoke.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.function.*;
import java.util.function.ObjLongConsumer;
import static com.alibaba.fastjson2.util.TypeUtils.*;
@SuppressWarnings("ALL")
public abstract class PropertyAccessorFactoryLambda extends PropertyAccessorFactory {
private static final boolean USE_UNSAFE = JDKUtils.UNSAFE != null;
private MethodHandles.Lookup lookup(Method method) {
return lookup(method.getDeclaringClass());
}
protected MethodHandles.Lookup lookup(Class<?> declaringClass) {
if (USE_UNSAFE) {
return JDKUtils.trustedLookup(declaringClass);
} else {
return MethodHandles.lookup().in(declaringClass);
}
}
/**
* Creates a Supplier that can instantiate objects using the given constructor
* via MethodHandle and LambdaMetafactory for better performance than reflection.
* If the MethodHandle approach fails, it falls back to the parent class implementation.
*
* @param constructor the constructor to use for object instantiation
* @return a Supplier that creates new instances using the provided constructor
*/
public Supplier createSupplier(Constructor constructor) {
try {
Class<?> declaringClass = constructor.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
MethodHandle methodHandle = lookup.findConstructor(
declaringClass,
MethodType.methodType(void.class)
);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"get",
METHOD_TYPE_SUPPLIER,
METHOD_TYPE_OBJECT,
methodHandle,
MethodType.methodType(declaringClass)
);
return (Supplier) callSite.getTarget().invokeExact();
} catch (Throwable ignored) {
// ignore
}
return super.createSupplier(constructor);
}
public Function createFunction(Constructor constructor) {
try {
Class<?> declaringClass = constructor.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
Class<?>[] parameterTypes = constructor.getParameterTypes();
Class<?> param0 = parameterTypes[0];
MethodHandle methodHandle = lookup.findConstructor(
declaringClass,
MethodType.methodType(void.class, param0)
);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"apply",
METHOD_TYPE_FUNCTION,
METHOD_TYPE_OBJECT_OBJECT,
methodHandle,
MethodType.methodType(declaringClass, box(param0))
);
return (Function) callSite.getTarget().invokeExact();
} catch (Throwable ignored) {
return super.createFunction(constructor);
}
}
public IntFunction createIntFunction(Constructor constructor) {
try {
Class<?> declaringClass = constructor.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
Class<?>[] parameterTypes = constructor.getParameterTypes();
Class<?> param0 = parameterTypes[0];
MethodHandle methodHandle = lookup.findConstructor(
declaringClass,
MethodType.methodType(void.class, param0)
);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"apply",
METHOD_TYPE_INT_FUNCTION,
METHOD_TYPE_OBJECT_INT,
methodHandle,
MethodType.methodType(declaringClass, box(param0))
);
return (IntFunction) callSite.getTarget().invokeExact();
} catch (Throwable ignored) {
return super.createIntFunction(constructor);
}
}
public LongFunction createLongFunction(Constructor constructor) {
try {
Class<?> declaringClass = constructor.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
Class<?>[] parameterTypes = constructor.getParameterTypes();
Class<?> param0 = parameterTypes[0];
MethodHandle methodHandle = lookup.findConstructor(
declaringClass,
MethodType.methodType(void.class, param0)
);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"apply",
METHOD_TYPE_LONG_FUNCTION,
METHOD_TYPE_OBJECT_LONG,
methodHandle,
MethodType.methodType(declaringClass, box(param0))
);
return (LongFunction) callSite.getTarget().invokeExact();
} catch (Throwable ignored) {
return super.createLongFunction(constructor);
}
}
public DoubleFunction createDoubleFunction(Constructor constructor) {
try {
Class<?> declaringClass = constructor.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
Class<?>[] parameterTypes = constructor.getParameterTypes();
Class<?> param0 = parameterTypes[0];
MethodHandle methodHandle = lookup.findConstructor(
declaringClass,
MethodType.methodType(void.class, param0)
);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"apply",
METHOD_TYPE_DOUBLE_FUNCTION,
METHOD_TYPE_OBJECT_DOUBLE,
methodHandle,
MethodType.methodType(declaringClass, box(param0))
);
return (DoubleFunction) callSite.getTarget().invokeExact();
} catch (Throwable ignored) {
return super.createDoubleFunction(constructor);
}
}
@Override
public BiFunction createBiFunction(Constructor constructor) {
try {
Class<?> declaringClass = constructor.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
Class<?>[] parameterTypes = constructor.getParameterTypes();
Class<?> param0 = parameterTypes[0];
Class<?> param1 = parameterTypes[1];
MethodHandle methodHandle = lookup.findConstructor(
declaringClass,
MethodType.methodType(void.class, param0, param1)
);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"apply",
METHOD_TYPE_BI_FUNCTION,
METHOD_TYPE_OBJECT_OBJECT_OBJECT,
methodHandle,
MethodType.methodType(declaringClass, box(param0), box(param1))
);
return (BiFunction) callSite.getTarget().invokeExact();
} catch (Throwable ignored) {
return super.createBiFunction(constructor);
}
}
static Class<?> box(Class cls) {
if (cls == int.class) {
return Integer.class;
}
if (cls == long.class) {
return Long.class;
}
if (cls == boolean.class) {
return Boolean.class;
}
if (cls == short.class) {
return Short.class;
}
if (cls == byte.class) {
return Byte.class;
}
if (cls == char.class) {
return Character.class;
}
if (cls == float.class) {
return Float.class;
}
if (cls == double.class) {
return Double.class;
}
return cls;
}
/**
* Creates a property accessor using getter and/or setter methods.
* This method delegates to the parent class's implementation.
*
* @param name the property name
* @param getter the getter method (optional, may be null)
* @param setter the setter method (optional, may be null)
* @return a PropertyAccessor instance for the specified getter/setter methods
*/
public PropertyAccessor createSupplier(String name, Method getter, Method setter) {
return super.create(name, null, null, getter, setter);
}
/**
* Creates a property accessor using getter and/or setter methods with explicit type information.
* This method attempts to optimize access by using LambdaMetafactory to create
* efficient functional interfaces for property access when possible.
*
* @param name the property name
* @param propertyClass the class of the property value
* @param propertyType the generic type of the property value
* @param getter the getter method (optional, may be null)
* @param setter the setter method (optional, may be null)
* @return a PropertyAccessor instance for the specified getter/setter methods
*/
@Override
public PropertyAccessor create(
String name, Class<?> propertyClass,
Type propertyType,
Method getter,
Method setter,
BiFunction<PropertyAccessor, Throwable, RuntimeException> exceptionHandler
) {
if (propertyClass == null) {
if (getter != null) {
propertyClass = getter.getReturnType();
} else {
Class<?>[] parameterTypes = setter.getParameterTypes();
if (parameterTypes.length == 1) {
propertyClass = parameterTypes[0];
} else if (parameterTypes.length == 2 && String.class.equals(parameterTypes[0])) {
propertyClass = parameterTypes[1];
}
}
}
Class<?> declaringClass;
if (getter != null) {
declaringClass = getter.getDeclaringClass();
} else {
declaringClass = setter.getDeclaringClass();
}
boolean lambda = declaringClass.getName().contains("$$Lambda");
if (!lambda && (setter == null || !isChainableSetter(setter))) {
if (propertyClass == boolean.class) {
return create(name, getBoolean(getter), setBoolean(setter));
}
if (JDKUtils.JVM_VERSION == 8) {
if (propertyClass == byte.class) {
return create(name, getByte(getter), setByte(setter));
}
if (propertyClass == short.class) {
return create(name, getShort(getter), setShort(setter));
}
if (propertyClass == char.class) {
return create(name, getChar(getter), setChar(setter));
}
}
if (propertyClass == int.class) {
return create(name, getInt(getter), setInt(setter));
}
if (propertyClass == long.class) {
return create(name, getLong(getter), setLong(setter));
}
if (propertyClass == float.class) {
return create(name, getFloat(getter), setFloat(setter));
}
if (propertyClass == double.class) {
return create(name, getDouble(getter), setDouble(setter));
}
if (!propertyClass.isPrimitive()) {
if (propertyType == null) {
if (getter != null) {
propertyType = getter.getGenericReturnType();
} else {
Type[] parameterTypes = setter.getGenericParameterTypes();
if (parameterTypes.length == 1) {
propertyType = parameterTypes[0];
} else if (parameterTypes.length == 2 && String.class.equals(parameterTypes[0])) {
propertyType = parameterTypes[1];
}
}
}
return create(name, propertyClass, propertyType, getObject(getter), setObject(name, setter), exceptionHandler);
}
}
return super.create(name, propertyClass, propertyType, getter, setter, exceptionHandler);
}
/**
* Validates that the method has the expected return type.
* Throws an IllegalArgumentException if the return type doesn't match.
*
* @param method the method to validate
* @param expectedReturnType the expected return type
*/
static void validateMethodAndReturnType(Method method, Class<?> expectedReturnType) {
if (!method.getReturnType().equals(expectedReturnType)) {
throw validateMethodAndReturnTypeEror(method, expectedReturnType);
}
}
/**
* Creates an IllegalArgumentException for method return type validation errors.
*
* @param method the method with the mismatched return type
* @param expectedReturnType the expected return type
* @return an IllegalArgumentException with details about the error
*/
private static IllegalArgumentException validateMethodAndReturnTypeEror(Method method, Class<?> expectedReturnType) {
return new IllegalArgumentException(
"Method return type mismatch. Expected: " + expectedReturnType.getSimpleName() +
", Actual: " + method.getReturnType().getSimpleName());
}
/**
* Creates a Predicate functional interface to access a boolean property via the given getter method.
* Uses LambdaMetafactory to create an efficient functional interface that wraps the method call.
*
* @param method the getter method for the boolean property
* @return a Predicate that can access the boolean property, or null if method is null
*/
public Predicate<Object> getBoolean(Method method) {
if (method == null) {
return null;
}
validateMethodAndReturnType(method, boolean.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (Predicate<Object>) LambdaMetafactory.metafactory(
lookup,
"test",
MethodType.methodType(Predicate.class),
MethodType.methodType(boolean.class, Object.class),
handle,
MethodType.methodType(boolean.class, method.getDeclaringClass())
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
/**
* Creates an ObjBoolConsumer functional interface to set a boolean property via the given setter method.
* Uses LambdaMetafactory to create an efficient functional interface that wraps the method call.
*
* @param method the setter method for the boolean property
* @return an ObjBoolConsumer that can set the boolean property, or null if method is null
*/
public ObjBoolConsumer setBoolean(Method method) {
if (method == null) {
return null;
}
validateMethodAndParameterType(method, boolean.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
BiConsumer<Object, Boolean> biConsumer = (BiConsumer<Object, Boolean>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(BiConsumer.class),
MethodType.methodType(void.class, Object.class, Object.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), Boolean.class)
).getTarget().invokeExact();
return biConsumer::accept;
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
/**
* Creates a ToByteFunction functional interface to access a byte property via the given getter method.
* For JDK 8, this implementation delegates to getInt and converts the result to byte.
*
* @param method the getter method for the byte property
* @return a ToByteFunction that can access the byte property, or null if method is null
*/
public ToByteFunction<Object> getByte(Method method) {
if (method == null) {
return null;
}
return o -> (byte) getInt(method).applyAsInt(o);
}
/**
* Creates a ToShortFunction functional interface to access a short property via the given getter method.
* For JDK 8, this implementation delegates to getInt and converts the result to short.
*
* @param method the getter method for the short property
* @return a ToShortFunction that can access the short property, or null if method is null
*/
public ToShortFunction<Object> getShort(Method method) {
if (method == null) {
return null;
}
return o -> (short) getInt(method).applyAsInt(o);
}
/**
* Creates a ToCharFunction functional interface to access a char property via the given getter method.
* For JDK 8, this implementation delegates to getInt and converts the result to char.
*
* @param method the getter method for the char property
* @return a ToCharFunction that can access the char property, or null if method is null
*/
public ToCharFunction<Object> getChar(Method method) {
if (method == null) {
return null;
}
return o -> (char) getInt(method).applyAsInt(o);
}
/**
* Creates a ToIntFunction functional interface to access an int property via the given getter method.
* Uses LambdaMetafactory to create an efficient functional interface that wraps the method call.
*
* @param method the getter method for the int property
* @return a ToIntFunction that can access the int property, or null if method is null
*/
public ToIntFunction<Object> getInt(Method method) {
if (method == null) {
return null;
}
Class<?> returnType = method.getReturnType();
if (!returnType.equals(int.class) && !returnType.equals(short.class) && !returnType.equals(byte.class) && !returnType.equals(char.class)) {
throw validateMethodAndReturnTypeEror(method, int.class);
}
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (ToIntFunction<Object>) LambdaMetafactory.metafactory(
lookup,
"applyAsInt",
MethodType.methodType(ToIntFunction.class),
MethodType.methodType(int.class, Object.class),
handle,
MethodType.methodType(int.class, method.getDeclaringClass())
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ToLongFunction<Object> getLong(Method method) {
if (method == null) {
return null;
}
validateMethodAndReturnType(method, long.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (ToLongFunction<Object>) LambdaMetafactory.metafactory(
lookup,
"applyAsLong",
MethodType.methodType(ToLongFunction.class),
MethodType.methodType(long.class, Object.class),
handle,
MethodType.methodType(long.class, method.getDeclaringClass())
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public Function<Object, Object> getObject(Method method) {
if (method == null) {
return null;
}
Class<?> declaringClass = method.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
try {
MethodHandle handle = lookup.unreflect(method);
return (Function<Object, Object>) LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
handle,
MethodType.methodType(method.getReturnType(), method.getDeclaringClass())
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ObjByteConsumer<Object> setByte(Method method) {
if (method == null) {
return null;
}
return (o, v) -> setInt(method).accept(o, (int) v);
}
public ObjCharConsumer<Object> setChar(Method method) {
if (method == null) {
return null;
}
return (o, v) -> setInt(method).accept(o, (int) v);
}
public ObjShortConsumer<Object> setShort(Method method) {
if (method == null) {
return null;
}
return (o, v) -> setInt(method).accept(o, (int) v);
}
public ObjIntConsumer<Object> setInt(Method method) {
if (method == null) {
return null;
}
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> paramType;
if (parameterTypes.length != 1 || (!(paramType = parameterTypes[0]).equals(int.class) && !paramType.equals(short.class) && !paramType.equals(byte.class) && !paramType.equals(char.class))) {
throw validateMethodAndParameterTypeError(int.class, parameterTypes);
}
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (ObjIntConsumer<Object>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(ObjIntConsumer.class),
MethodType.methodType(void.class, Object.class, int.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), paramType)
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ObjLongConsumer<Object> setLong(Method method) {
if (method == null) {
return null;
}
validateMethodAndParameterType(method, long.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (ObjLongConsumer<Object>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(ObjLongConsumer.class),
MethodType.methodType(void.class, Object.class, long.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), long.class)
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ToFloatFunction<Object> getFloat(Method method) {
if (method == null) {
return null;
}
validateMethodAndReturnType(method, float.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
ToDoubleFunction<Object> toDouble = (ToDoubleFunction<Object>) LambdaMetafactory.metafactory(
lookup,
"applyAsDouble",
MethodType.methodType(ToDoubleFunction.class),
MethodType.methodType(double.class, Object.class),
handle,
MethodType.methodType(float.class, method.getDeclaringClass())
).getTarget().invokeExact();
return (ToFloatFunction<Object>) (obj) -> (float) toDouble.applyAsDouble(obj);
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ObjFloatConsumer setFloat(Method method) {
if (method == null) {
return null;
}
validateMethodAndParameterType(method, float.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
BiConsumer<Object, Float> setDouble = (BiConsumer<Object, Float>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(BiConsumer.class),
MethodType.methodType(void.class, Object.class, Object.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), Float.class)
).getTarget().invokeExact();
return (obj, value) -> setDouble.accept(obj, (float) value);
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ToDoubleFunction<Object> getDouble(Method method) {
if (method == null) {
return null;
}
validateMethodAndReturnType(method, double.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (ToDoubleFunction<Object>) LambdaMetafactory.metafactory(
lookup,
"applyAsDouble",
MethodType.methodType(ToDoubleFunction.class),
MethodType.methodType(double.class, Object.class),
handle,
MethodType.methodType(double.class, method.getDeclaringClass())
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
public ObjDoubleConsumer<Object> setDouble(Method method) {
if (method == null) {
return null;
}
validateMethodAndParameterType(method, double.class);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (ObjDoubleConsumer<Object>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(ObjDoubleConsumer.class),
MethodType.methodType(void.class, Object.class, double.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), double.class)
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
/**
* Creates a BiConsumer functional interface to set an Object property via the given setter method.
* Uses LambdaMetafactory to create an efficient functional interface that wraps the method call.
*
* @param method the setter method for the Object property
* @return a BiConsumer that can set the Object property, or null if method is null
*/
public BiConsumer<Object, Object> setObject(Method method) {
return setObject(null, method);
}
/**
* Creates a BiConsumer functional interface to set an Object property via the given setter method,
* with the option to pass a property name for chainable setters.
* Uses LambdaMetafactory to create an efficient functional interface that wraps the method call.
*
* @param name the property name (for chainable setters)
* @param method the setter method for the Object property
* @return a BiConsumer that can set the Object property, or null if method is null
*/
public BiConsumer<Object, Object> setObject(String name, Method method) {
if (method == null) {
return null;
}
MethodHandles.Lookup lookup = lookup(method);
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 2
&& parameterTypes[0].equals(String.class)
) {
BiFunction<String, Object, Object> biFunction;
try {
MethodHandle handle = lookup.unreflect(method);
biFunction = (BiFunction<String, Object, Object>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(BiFunction.class),
MethodType.methodType(void.class, Object.class, Object.class, Object.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), String.class, method.getParameterTypes()[1])
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
return (obj, value) -> biFunction.apply(name, value);
}
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Method must have exactly one parameter");
}
try {
MethodHandle handle = lookup.unreflect(method);
return (BiConsumer<Object, Object>) LambdaMetafactory.metafactory(
lookup,
"accept",
MethodType.methodType(BiConsumer.class),
MethodType.methodType(void.class, Object.class, Object.class),
handle,
MethodType.methodType(void.class, method.getDeclaringClass(), method.getParameterTypes()[0])
).getTarget().invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Failed to create lambda for method: " + method, e);
}
}
/**
* Checks if the given method is a chainable setter (fluent setter).
* A chainable setter is one that returns the same type as the declaring class,
* allowing for method chaining.
*
* @param method the method to check
* @return true if the method is a chainable setter, false otherwise
*/
static boolean isChainableSetter(Method method) {
return method.getReturnType() == method.getDeclaringClass();
}
/**
* Validates that the method has exactly one parameter of the expected type.
* Throws an IllegalArgumentException if the parameter type doesn't match.
*
* @param method the method to validate
* @param expectedParameterType the expected parameter type
*/
static void validateMethodAndParameterType(Method method, Class<?> expectedParameterType) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1 || !parameterTypes[0].equals(expectedParameterType)) {
throw validateMethodAndParameterTypeError(expectedParameterType, parameterTypes);
}
}
/**
* Creates an IllegalArgumentException for method parameter type validation errors.
*
* @param expectedParameterType the expected parameter type
* @param parameterTypes the actual parameter types
* @return an IllegalArgumentException with details about the error
*/
private static IllegalArgumentException validateMethodAndParameterTypeError(Class<?> expectedParameterType, Class<?>[] parameterTypes) {
return new IllegalArgumentException(
"Method parameter type mismatch. Expected: " + expectedParameterType.getSimpleName() +
", Actual: " + (parameterTypes.length > 0 ? parameterTypes[0].getSimpleName() : "no parameters"));
}
}