Yes, generics are a recurrent subject, and here it goes again…
Currently, I have this code. Processor is a “simple”, generic interface.
public final class ProcessorChain<IN extends MessageProvider, OUT extends MessageProvider>
implements Processor<IN, OUT>
{
/**
* The resulting processor
*/
private final Processor<IN, OUT> p;
/**
* Start a processing chain with a single processor
*
* @param p the processor
* @param <X> the input type
* @param <Y> the output type
* @return a single element processing chain
*/
public static <X extends MessageProvider, Y extends MessageProvider>
ProcessorChain<X, Y> startWith(final Processor<X, Y> p)
{
return new ProcessorChain<X, Y>(p);
}
/**
* Private constructor
*
* @param p the processor
*/
private ProcessorChain(final Processor<IN, OUT> p)
{
this.p = p;
}
/**
* Add an existing processor to that chain
*
* <p>Note that this returns a <b>new</b> chain.</p>
*
* @param p2 the processor to add
* @param <NEWOUT> the return type for that new processor
* @return a new chain consisting of the previous chain with the new
* processor appended
*/
public <NEWOUT extends MessageProvider> ProcessorChain<IN, NEWOUT>
chainWith(final Processor<OUT, NEWOUT> p2)
{
return new ProcessorChain<IN, NEWOUT>(merge(p, p2));
}
public Processor<IN, OUT> end()
{
return this;
}
@Override
public OUT process(final ProcessingReport report, final IN input)
throws ProcessingException
{
return p.process(report, input);
}
/**
* Merge two processors
*
* @param p1 the first processor
* @param p2 the second processor
* @param <X> the input type of {@code p1}
* @param <Y> the output type of {@code p1} and input type of {@code p2}
* @param <Z> the output type of {@code p2}
* @return a processor resulting of applying {@code p2} to the output of
* {@code p1}
*/
private static <X extends MessageProvider, Y extends MessageProvider, Z extends MessageProvider>
Processor<X, Z> merge(final Processor<X, Y> p1, final Processor<Y, Z> p2)
{
return new Processor<X, Z>()
{
@Override
public Z process(final ProcessingReport report, final X input)
throws ProcessingException
{
return p2.process(report, p1.process(report, input));
}
};
}
}
So, currently, I can write:
// p1 does <X, Y>, p2 does <Y, Z>, p3 does <Z, T>
Processor<X, T> p = ProcessingChain.startWith(p1).chainWith(p2).chainWith(p3).end();
And it works. In fact, I’ve never been that far into generics.
What I’d like to be able to write is this:
Processor<X, T> p = ProcessingChain.start().add(p1).add(p2).add(p3).end();
I’ve been battling trying to do this, but failed.
One reason I want to be able to do this is also, in the future, to be able to write:
Processor<X, T> p = ProcessingChain.start().add(p1).stopOnError().etc().etc()
And I feel that if I know how to .start(), “utility” processors along the way won’t be a problem.
So, how do do that?
What should a parameterless
startmethod return? This is the crux of the issue, though you’ve only alluded to it in your question.Note that you’ve declared
ProcessorChainas implementingProcessor– so any instance ofProcessorChainmust act like one. For that reason, creating aProcessorChaininstance without aProcessorto wrap doesn’t make sense.In general, I would say that a builder should not implement the interface of what it builds, but having written that I think I’d at least be interested in seeing counter-arguments.