2

I have this unusual (I think?) situation

public abstract class Base {

   public Base() {
       // code
   }

   public final void executeAll() {
      final Foo foo = new Foo();
      final Bar bar = new Bar();
      // now execute all method of "this" that: 
      //   - have annotation mark
      //   - have return type void
      //   - accept (foo, bar) has parameters
      //   - name can be whatever you want
      //   - protected
   }

   @Retention(RetentionPolicy.RUNTIME)
   @Target({ElementType.METHOD})
   protected @interface Mark{
   }
}

Now I have a class that extends Base and has some methods with the signature as explained in the executeAll method, like this:

public class Test extends Base {

   @Mark
   protected void willBeCalled(Foo a, Bar b) {
   }

   @Mark
   protected void alsoThisOne(Foo a, Bar b) {
   }

   @Mark
   protected void yep(Foo a, Bar b) {
   }

}

I know that this can be easily achieved my creating an "protected abstract Collection<BiConsumer<Foo, Bar>> getToBeCalled()" make every child to extend it and write the executeAll() method as:

   protected abstract Collection<BiConsumer<Foo, Bar>> getToBeCalled();

   public final void executeAll() {
      final Foo foo = new Foo();
      final Bar bar = new Bar();
      getToBeCalled().stream.forEach(bic-> bic.accept(foo, bar));
   }

But this is not what I want to achieve. I need a way to get the methods to execute from the child class without any major intervention from the developer (that why I thought about an annotation to mark the method desired to be executed and nothing more).

Right now I have a working implementation that inside Base empty constructor scan for every available methods with this.getClass().getMethods(), filter them (must have Mark annotation, accept only a Foo parameter and a Bar parameter in that order and return void, be protected) and save them in a private List<Method> methods; later used by executeAll to call each method.

Recently I also read this: https://www.optaplanner.org/blog/2018/01/09/JavaReflectionButMuchFaster.html ...

... and just-for-fun implemented a MethodHandle version (I think that maybe in my use case will be faster), and planning to make a LambdaMetafactory version... but I'm really intrigued by the javax.tools.JavaCompiler solution. I'm still trying to figuring out how to do it tho... I keep my original method name search and filtering, I keep only the names... and now? How can I convert a list of method names (which I know I have access to, since I'm calling from "this" and are declared protected in the child class, and also have a specific signature) to something usable, like a single private final BiConsumer<Foo, Bar> callAll; computed and saved by the Base() constructor and called by the executeAll function like this:

public abstract class Base {

   private final BiConsumer<Foo, Bar> callAll;

   protected Base() {
       // get all methods <<< already done
       Method[] methods = ;
       // filter them by annotation, visibility and signature <<< already done
       List<Method> filtered> = ;
       // save only the names <<< already done
       List<String> names = ;
       // use javax.tools.JavaCompiler somehow <<< I miss this
       callAll = ???; 
   }

   public final void executeAll() {
      final Foo foo = new Foo();
      final Bar bar = new Bar();
      callAll.accept(foo, bar);
   }

   @Retention(RetentionPolicy.RUNTIME)
   @Target({ElementType.METHOD})
   protected @interface Mark{
   }
}

Of course the idea to use a BiConsumer as callAll is just an idea if there is something better I'm perfectly fine with suggestion.

Slaw
  • 37,820
  • 8
  • 53
  • 80
Vento
  • 63
  • 7
  • 2
    I don't think the `JavaCompiler` API can help you here, or if it can then the solution will be incredibly convoluted. Per your requirements, reflection is probably the easiest solution. But note that protected methods are not returned by `getMethods()`, you have to use `getDeclaredMethods()`, which means you have to walk up the class hierarchy and, if necessary, handle overridden methods where both have the annotation gracefully (e.g. only include the original method). Going a step further and converting a `BiConsumer` via `LambdaMetaFactory` may be more effort than it's worth. – Slaw Apr 03 '20 at 14:06
  • 2
    But honestly, it may be better to simply have an abstract, void-returning method that you invoke and leave it up to the developer of a subclass to do all that needs to be done. Or provide a way for the developer of a subclass to register `BiConsumer` instances. Reflection should only be used if absolutely necessary as it's more complicated, slower, and bypasses compile-time checks. – Slaw Apr 03 '20 at 14:09

0 Answers0