
org.jboss.weld.invokable.MethodHandleUtils Maven / Gradle / Ivy
package org.jboss.weld.invokable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.invoke.Invoker;
import org.jboss.weld.logging.InvokerLogger;
class MethodHandleUtils {
private MethodHandleUtils() {
}
static final MethodHandle CLEANUP_ACTIONS_CTOR;
static final MethodHandle CLEANUP_FOR_VOID;
static final MethodHandle CLEANUP_FOR_NONVOID;
static final MethodHandle LOOKUP;
static final MethodHandle REPLACE_PRIMITIVE_LOOKUP_NULLS;
static final MethodHandle THROW_VALUE_CARRYING_EXCEPTION;
static final MethodHandle TRIM_ARRAY_TO_SIZE;
static {
try {
CLEANUP_ACTIONS_CTOR = createMethodHandle(CleanupActions.class.getDeclaredConstructor());
String runName = "run";
CLEANUP_FOR_VOID = createMethodHandle(CleanupActions.class.getMethod(
runName, Throwable.class, CleanupActions.class));
CLEANUP_FOR_NONVOID = createMethodHandle(CleanupActions.class.getMethod(
runName, Throwable.class, Object.class, CleanupActions.class));
LOOKUP = createMethodHandle(LookupUtils.class.getDeclaredMethod(
"lookup", CleanupActions.class, BeanManager.class, Type.class, Annotation[].class));
REPLACE_PRIMITIVE_LOOKUP_NULLS = MethodHandleUtils.createMethodHandle(LookupUtils.class.getDeclaredMethod(
"replacePrimitiveLookupNulls", Object[].class, Class[].class, boolean[].class));
THROW_VALUE_CARRYING_EXCEPTION = createMethodHandle(ValueCarryingException.class.getDeclaredMethod(
"throwReturnValue", Object.class));
TRIM_ARRAY_TO_SIZE = createMethodHandle(ArrayUtils.class.getDeclaredMethod(
"trimArrayToSize", Object[].class, int.class));
} catch (NoSuchMethodException e) {
// should never happen
throw new IllegalStateException("Unable to locate Weld internal helper method", e);
}
}
private static MethodHandles.Lookup lookupFor(Executable method) throws IllegalAccessException {
if (Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
return MethodHandles.publicLookup();
}
// to create a method handle for a `protected`, package-private or `private` method,
// we need a private lookup in the declaring class
Module thisModule = MethodHandleUtils.class.getModule();
Class> targetClass = method.getDeclaringClass();
Module targetModule = targetClass.getModule();
if (!thisModule.canRead(targetModule)) {
// we need to read the other module in order to have privateLookup access
// see javadoc for MethodHandles.privateLookupIn()
thisModule.addReads(targetModule);
}
return MethodHandles.privateLookupIn(targetClass, MethodHandles.lookup());
}
static MethodHandle createMethodHandle(Method method) {
try {
return lookupFor(method).unreflect(method);
} catch (ReflectiveOperationException e) {
throw InvokerLogger.LOG.cannotCreateMethodHandle(method, e);
}
}
static MethodHandle createMethodHandle(Constructor> constructor) {
try {
return lookupFor(constructor).unreflectConstructor(constructor);
} catch (ReflectiveOperationException e) {
throw InvokerLogger.LOG.cannotCreateMethodHandle(constructor, e);
}
}
static MethodHandle createMethodHandleFromTransformer(Method targetMethod, TransformerMetadata transformer,
Class> transformationArgType) {
List matchingMethods = new ArrayList<>();
// transformers must be `public` and may be inherited (if not `static`)
for (Method m : transformer.getDeclaringClass().getMethods()) {
// `static` transformers must be declared directly on the given class,
// instance transformers may be inherited
if (Modifier.isStatic(m.getModifiers()) && !m.getDeclaringClass().equals(transformer.getDeclaringClass())) {
continue;
}
// method match is only based on class and name, no match is a problem and so are multiple
if (m.getName().equals(transformer.getMethodName())) {
matchingMethods.add(m);
}
}
if (matchingMethods.isEmpty()) {
throw InvokerLogger.LOG.noMatchingTransformerMethod(transformer);
}
if (matchingMethods.size() > 1) {
throw InvokerLogger.LOG.multipleMatchingTransformerMethod(transformer, matchingMethods);
}
Method method = matchingMethods.get(0);
// validate method
validateTransformerMethod(method, transformer, transformationArgType);
MethodHandle result = createMethodHandle(method);
// for input transformers, we might need to change return type to whatever the original method expects
// for output transformers, we might need to change their input params
// this enables transformers to operate on subclasses (input tf) or superclasses (output tf)
if (transformer.isInputTransformer()
&& !result.type().returnType().equals(transformationArgType)) {
result = result.asType(result.type().changeReturnType(transformationArgType));
} else if (transformer.isOutputTransformer()
&& result.type().parameterCount() > 0
&& !result.type().parameterType(0).equals(transformationArgType)) {
result = result.asType(result.type().changeParameterType(0, transformationArgType));
}
if (TransformerType.EXCEPTION.equals(transformer.getType())) {
// if assignable, then just alter return type
if (targetMethod.getReturnType().isAssignableFrom(result.type().returnType())) {
// exception handlers can return a subtype of original class
// so long as it is assignable, just cast it to the required/expected type
result = result.asType(result.type().changeReturnType(targetMethod.getReturnType()));
} else {
// if not assignable, then we need to apply a return value transformer which hides the value in an exception
MethodHandle throwReturnValue = THROW_VALUE_CARRYING_EXCEPTION;
// cast return value of the custom method we use to whatever the original method expects - we'll never use it anyway
throwReturnValue = throwReturnValue
.asType(throwReturnValue.type().changeReturnType(targetMethod.getReturnType()));
// adapt the parameter type as well, we don't really care what it is, we just store it and throw it
throwReturnValue = throwReturnValue
.asType(throwReturnValue.type().changeParameterType(0, result.type().returnType()));
result = MethodHandles.filterReturnValue(result, throwReturnValue);
}
}
return result;
}
private static void validateTransformerMethod(Method m, TransformerMetadata transformer, Class> transformationArgType) {
// all transformers have to be public to ensure accessibility
if (!Modifier.isPublic(m.getModifiers())) {
throw InvokerLogger.LOG.nonPublicTransformer(transformer);
}
int paramCount = m.getParameterCount();
if (transformer.isInputTransformer()) {
// input transformers need to validate assignability of their return type versus original arg type
if (!transformationArgType.isAssignableFrom(m.getReturnType())) {
throw InvokerLogger.LOG.inputTransformerNotAssignable(transformer, transformationArgType);
}
// instance method is no-param, otherwise its 1-2 with second being Consumer
if (!Modifier.isStatic(m.getModifiers())) {
if (paramCount != 0) {
throw InvokerLogger.LOG.nonStaticInputTransformerHasParams(transformer);
}
} else {
if (paramCount > 2) {
throw InvokerLogger.LOG.staticInputTransformerParams(transformer);
}
if (paramCount == 2) {
// we do not validate type param of Consumer, i.e. if it's exactly Consumer
if (!Consumer.class.equals(m.getParameters()[1].getType())) {
throw InvokerLogger.LOG.staticInputTransformerIncorrectParams(transformer);
}
}
}
} else if (transformer.isOutputTransformer()) {
// output transformers need to validate assignability of their INPUT
// this also means instance methods need no validation in this regard
if (!Modifier.isStatic(m.getModifiers())) {
if (paramCount != 0) {
throw InvokerLogger.LOG.nonStaticOutputTransformerHasParams(transformer);
}
} else {
if (paramCount != 1) {
throw InvokerLogger.LOG.staticOutputTransformerParams(transformer);
}
if (!m.getParameters()[0].getType().isAssignableFrom(transformationArgType)) {
throw InvokerLogger.LOG.outputTransformerNotAssignable(transformer, transformationArgType);
}
}
} else {
// wrapper has exactly three arguments
Class>[] params = m.getParameterTypes();
if (params.length == 3
&& params[0].equals(transformationArgType)
&& params[1].equals(Object[].class)
&& params[2].equals(Invoker.class)) {
// OK
} else {
throw InvokerLogger.LOG.wrapperUnexpectedParams(transformer, transformationArgType);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy