I want to create a processor factory that uses CDI to get all available processors. The factory should select the desired processor based on some parameter. So I have my parameter:
public abstract class Parameter { }
@CorrespondingProcessor(type = StringProcessor.class)
public class StringParameter extends Parameter { }
@CorrespondingProcessor(type = FileProcessor.class)
public class FileParameter extends Parameter { }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CorrespondingProcessor {
Class<? extends Processor<? extends Parameter>> type();
}
And for every parameter there is a processor:
public interface Processor<T extends Parameter> {
String work(T parameter);
}
public class StringProcessor implements Processor<StringParameter> {
@Override
public String work(StringParameter parameter) {
return "string";
}
}
public class FileProcessor implements Processor<FileParameter> {
@Override
public String work(FileParameter parameter) {
return "file";
}
}
Basically my factory looks like this:
public class ProcessorFactory {
private final Instance<Processor<? extends Parameter>> processors;
@Inject
public ProcessorFactory(@Any Instance<Processor<? extends Parameter>> processors) {
this.processors = processors;
}
public <T extends Parameter> Processor<T> getProcessor(T parameter) {
CorrespondingProcessor annotation = parameter.getClass().getAnnotation(CorrespondingProcessor.class);
CorrespondingProcessorLiteral correspondingProcessorLiteral = new CorrespondingProcessorLiteral(annotation.type());
Class<? extends Processor<? extends Parameter>> type = correspondingProcessorLiteral.type();
Processor<? extends Parameter> processor = processors.select(type).get();
return processor;
}
private class CorrespondingProcessorLiteral extends AnnotationLiteral<CorrespondingProcessor> implements CorrespondingProcessor {
private final Class<? extends Processor<? extends Parameter>> type;
public CorrespondingProcessorLiteral(Class<? extends Processor<? extends Parameter>> type) {
this.type = type;
}
@Override
public Class<? extends Processor<? extends Parameter>> type() {
return type;
}
}
}
This will not compile, because the defined return type of the getProcessor
-method differs from the returned processor
-variable. The compiler does not know if the selected processor is a processor of T. And at this point I am struggling. Is there a way to get this working? How can I enforce that the selected processor is a processor of T? Or is this approach wrong in the first place?
I don't want to inject all the available processors by their own. Because this would mean, I would have to change the factory every time I add a new processor.
CodePudding user response:
The solution is using TypeLiteral
instead of AnnotationLiteral
. But as discussed in What type literal must I use to have CDI's Instance::select method work properly? one must provide the actual type at compile time.
processors.select(new TypeLiteral<Processor<T>>() {}).get()
This will not work since type T is erased due to type erasure.
So the getProcessor
-method must be changed to:
public <T extends Parameter> Processor<T> getProcessor(TypeLiteral<Processor<T>> selector) {
return processors.select(selctor).get();
}
Calling this method like factory.getProcessor(new TypeLiteral<Processor<StringParameter>>() {})
will do the trick.