Home > database >  Factory using CDI returning capture of type instead of concrete type
Factory using CDI returning capture of type instead of concrete type

Time:04-06

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.

  • Related