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?
What you are doing is inherently not type safe. How do you ensure that
interfaces.get(0)can process aChildOne? What if it takes aChildTwoinstead?This is why you are not allowed to pass
childOnetointerfaces.get(0).process.interfaces.get(0)is aProcessInterface<Something>, but you don't know whatSomethingis. It could beChildOne, orChildTwo. This is what the typeProcessInterface<? extends AbstractParent>means. See also PECS.If
Somethingwere actuallyChildTwo, then passing aChildOnewill not work at all. The implementing method does not know how to deal with that. In fact, you can only safely passnulltoprocess.So let's suppose the user's of your library are responsible for ensuring that type safety, and that you are sure that this is always safe.
In that case, cast
generate's return value toList<ProcessorInterface<AbstractParent>>:(You can change the return type of
generateto this type, if you really want.)ProcessInterface<AbstractParent>is a type that you can pass all kinds ofAbstractParents to itsprocessmethod. In fact, since type safety is gone at this point, it doesn't matter which exact type you cast to, as long as it can take anyAbstractParent.Now if you happen to pass the wrong type of instance to
process, aClassCastExceptionwill be thrown.As for your
LambdaMetafactory, you should pass the method type of the interface method for the fourth argument ofmetafactory, not the implementing method's method type.