mercredi 12 juillet 2023

LambdaMetaFactory with Generic Static Methods?

So I'm creating a library that allows users to pass a Class<?> and collect all static methods with a specific annotation (and other criteria, such as a certain parameter count and types) and convert them into lambda FunctionalInterfaces that my library will use internally for processing.

For example:

Say I have the following class tree:

public abstract class AbstractParent {
  
  public String sayHi() {
    return getClass().getName() + " instance says hi!";
  }

}

with subclasses:

public class ChildOne extends AbstractParent {

  public int childOneSpecialMethod() {
    return 2558445;
  }
}
public class ChildTwo extends AbstractParent {

  public int childTwoSpecialMethod() {
    return 484848;
  }
  
}

My library allows for users to annotate a class's static methods with the following annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ProcessAnnotation {}

with the following rules: the static method first parameter be an instance of AbstractParent, and its second parameter must be a String, and must return a String. So, something like this:

public class GeneralProcessor {
  
  @ProcessAnnotation
  public static String easyProcessing(ChildOne one, String otherArg) {
    //Some code
    System.out.println(" === In processing for ChildOne types");
    return otherArg + one.toString();
  }

  @ProcessAnnotation
  public static String easyProcessing(ChildTwo two, String otherArg) {
    //Some code
    System.out.println(" === In processing for ChildTwo types");
    return otherArg + two.toString();
  }
}

On the library-side of things, I want to collect all these methods so that I can use them for some processing while doing it in a relatively fast manner and I found that MethodHandles and LambdaMetaFactory is the best way to do this.

Specifically, I want to invoke these collected methods using my own FunctionalInterface :

@FunctionalInterface
public interface ProcessInterface<T extends AbstractParent> {
  
  public String process(T obj, String extraArg);
}

So far, what I've tried is something like this:

public static List<ProcessInterface<? extends AbstractParent>> generate(Class<?> targetClass) throws Throwable {
    ArrayList<ProcessInterface<? extends AbstractParent>> processors = new ArrayList<>();

    for (Method method : targetClass.getDeclaredMethods()) {
      if (method.isAnnotationPresent(ProcessAnnotation.class) &&
          method.getParameterCount() == 2 && 
          AbstractParent.class.isAssignableFrom(method.getParameterTypes()[0]) &&
          method.getParameterTypes()[1] == String.class) {
        
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle handle = lookup.unreflect(method);
        CallSite callSite = LambdaMetafactory.metafactory(lookup, 
                                                          "process", 
                                                          MethodType.methodType(ProcessInterface.class), 
                                                          MethodType.methodType(String.class, 
                                                                                method.getParameterTypes()[0],
                                                                                String.class), 
                                                          handle, 
                                                          handle.type());

        ProcessInterface<? extends AbstractParent> func = (ProcessInterface<? extends AbstractParent>) callSite.getTarget().invoke();
        processors.add(func);
      }
    }

    return processors;
  }

However, I get the following error when I actually invoke the lambda. For example:

List<ProcessInterface<? extends AbstractParent>> interfaces = generate(GeneralProcessor.class);

ChildOne childOne = new ChildOne();
interfaces.get(0).process(childOne, "");

Is there a fix to do this? Or maybe even a better way to achieve this?





Aucun commentaire:

Enregistrer un commentaire