Reputation: 25
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?
Upvotes: 1
Views: 105
Reputation: 271175
What you are doing is inherently not type safe. How do you ensure that interfaces.get(0)
can process a ChildOne
? What if it takes a ChildTwo
instead?
This is why you are not allowed to pass childOne
to interfaces.get(0).process
. interfaces.get(0)
is a ProcessInterface<Something>
, but you don't know what Something
is. It could be ChildOne
, or ChildTwo
. This is what the type ProcessInterface<? extends AbstractParent>
means. See also PECS.
If Something
were actually ChildTwo
, then passing a ChildOne
will not work at all. The implementing method does not know how to deal with that. In fact, you can only safely pass null
to process
.
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 to List<ProcessorInterface<AbstractParent>>
:
List<ProcessInterface<AbstractParent>> interfaces =
(List<ProcessInterface<AbstractParent>>)(Object)generate(GeneralProcessor.class);
(You can change the return type of generate
to this type, if you really want.)
ProcessInterface<AbstractParent>
is a type that you can pass all kinds of AbstractParent
s to its process
method. 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 any AbstractParent
.
Now if you happen to pass the wrong type of instance to process
, a ClassCastException
will be thrown.
As for your LambdaMetafactory
, you should pass the method type of the interface method for the fourth argument of metafactory
, not the implementing method's method type.
CallSite callSite = LambdaMetafactory.metafactory(lookup,
"process",
MethodType.methodType(ProcessInterface.class),
// this should be the interface method's type
MethodType.methodType(String.class, AbstractParent.class, String.class),
handle,
handle.type());
Upvotes: 1