javagenericstype-inference

Composite generic validator in java


I'm trying to create a composite validator for a set of APIs. The generic interface is:

public interface APIValidator<Input, Context> {
     boolean validate(Input i, Context c);
}

For example, if there are 2 APIs: Send and Accept, I have "marker" interfaces:

public interface SendAPIValidator implements APIValidator<SendInput, SendContext> {
   boolean validate(SendInput i, SendContext c);
}

public interface AcceptAPIValidator implements APIValidator<AcceptInput, AcceptContext> {
   boolean validate(AcceptInput i, AcceptContext c);
}

And these, respectively, have implementations.

Then I have a static class to create a composite validator:

class Validators {
   public static <I, C> APIValidator<I, C> composite(Set<APIValidator<I, C>> validators) {
        return (input, context) -> { 
            return validators.stream()
                    .allMatch(validator -> validator.validate(input, context));
        }

}

However, trying to inject this into a provider or something doesn't work:

SendAPIValidator provideCompositeValidator(Set<SendAPIValidator> validators) {
   // Error: no instance(s) of type variable(s) C, I exist so that SendAPIValidator conforms to APIValidator<I, C> 
   return Validators.compositeValidator(validators);
}

Is this because of Java's type invariants (I forget the technical term)? I know it's possible to do this if I pass in APIValidator<SendInput, SendContext> instead. Is there another way?


Solution

  • As explained in the comments, you have to declare the input parameter with a bounded wildcard (see PECS) to make it compatible with Set<SendAPIValidator>:

    public static <I, C> APIValidator<I, C> composite(Set<? extends APIValidator<I, C>> validators) {
        return (input, context) -> validators.stream()
                .allMatch(validator -> validator.validate(input, context));
    }
    

    The other problem is that this method returns an instance of APIValidator which is neither assignable nor castable to SendAPIValidator. However, since they share the same functional signature, you can can easily convert it to the desired type with a method reference:

    SendAPIValidator provideCompositeValidator(Set<SendAPIValidator> validators) {
        return Validators.composite(validators)::validate;
    }