vendredi 27 octobre 2023

MethodHandle Lookup Issues

I've recently needed to make a relatively simple events system. (Id -> Consumer) This would be able to subscribe a Method based off annotation. Then I remembered that Java isn't C# and attempted to look for a way to accomplish this. After many similar threads, I came across this GitHub Gist, and attempted to implement it. I did so successfully, however did not realize early enough that the MethodHandles.lookup() method is location specific and only applies to the class that calls it.

ConsumerFactory

public class ConsumerFactory {
    private final Method consumerMethod;
    private final MethodType consumerMethodType;

    private final Map<Method, Consumer<?>> methodCache = new HashMap<>();

    public ConsumerFactory() {
        consumerMethod = findLambdaMethod(Consumer.class);
        consumerMethodType = MethodType.methodType(consumerMethod.getReturnType(), consumerMethod.getParameterTypes());
    }

    public <T, L> Consumer<T> createConsumer(L instance, Method implMethod) throws Throwable {
        Consumer<T> cached = (Consumer<T>) methodCache.get(implMethod);
        if(cached==null) {
            Class<?> implType = implMethod.getDeclaringClass();

            MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);
            MethodType implMethodType = MethodType.methodType(implMethod.getReturnType(), implMethod.getParameterTypes());
            MethodHandle implMethodHandle = lookup.findVirtual(implType, implMethod.getName(), implMethodType);

            MethodType invokedMethodType = MethodType.methodType(Consumer.class, implType);

            CallSite metaFactory = LambdaMetafactory.metafactory(
                    lookup,
                    consumerMethod.getName(), invokedMethodType, consumerMethodType,
                    implMethodHandle, implMethodType);

            MethodHandle factory = metaFactory.getTarget();
            Consumer<T> consumer = (Consumer<T>) factory.invoke(instance);
            methodCache.put(implMethod, consumer);
            return consumer;
        }
        return cached;
    }

    private Method findLambdaMethod(Class<?> type) {
        if (!type.isInterface()) {
            throw new IllegalArgumentException("This must be interface: " + type);
        }
        Method[] methods = getAllMethods(type);
        if (methods.length == 0) {
            throw new IllegalArgumentException("No methods in: " + type.getName());
        }
        Method targetMethod = null;
        for (Method method : methods) {
            if (isInterfaceMethod(method)) {
                if (targetMethod != null) {
                    throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
                }
                targetMethod = method;
            }
        }
        if (targetMethod == null) {
            throw new IllegalArgumentException("No method in: " + type.getName());
        }
        return targetMethod;
    }
    private Method[] getAllMethods(Class<?> type) {
        LinkedList<Method> result = new LinkedList<>();
        Class<?> current = type;
        do {
            result.addAll(Arrays.asList(current.getMethods()));
        } while ((current = current.getSuperclass()) != null);
        return result.toArray(new Method[0]);
    }
    private boolean isInterfaceMethod(Method method) {
        return !method.isDefault() && Modifier.isAbstract(method.getModifiers());
    }
}

Is their anything I could do to still have this sort of idea function? Ideally end up as something like Map<String, Consumer<Event>>. I was considering forcefully creating a Lookup class, but I'm not sure if that would be the best idea.

Thank you all for the help!





Aucun commentaire:

Enregistrer un commentaire