I've run into a sticky problem that I can't seem to solve with java generics. This is a bit complicated, but I couldn't think of a simpler scenario to illustrate the problem... Here goes:
I have a Processor class that requires a Context. There are different types of Context; most processors just need any abstract Context, but others require a specific subclass. Like this:
abstract class AbstractProcessor<C extends Context> {
public abstract void process(C context);
}
class BasicProcessor extends AbstractProcessor<Context> {
@Override
public void process(Context context) {
// ... //
}
}
class SpecificProcessor extends AbstractProcessor<SpecificContext> {
@Override
public void process(SpecificContext context) {
// ... //
}
}
Ok, cool: Processors can declare the type of Context they need, and they can assume the right type will be passed into process() without casting.
Now, I have a Dispatcher class that owns a mapping of Strings to Processors:
class Dispatcher<C extends Context> {
Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();
public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
processorMap.put(name, processor);
}
public void dispatch(String name, C context) {
processorMap.get(name).process(context);
}
}
Ok, so far so good! I can create a Dispatcher for a specific type of Context, then register a batch of processors that may expect any abstraction of that Context type.
Now, here's the problem: I want the abstract Context type to own the Dispatcher, and derived Context types should be able to register additional Processors. Here's the closest I could find to a working solution, but it doesn't fully work:
class Context<C extends Context> {
private final Dispatcher<C> dispatcher = new Dispatcher<C>();
public Context() {
// every context supports the BasicProcessor
registerProcessor("basic", new BasicProcessor());
}
protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
dispatcher.registerProcessor(name, processor);
}
public void runProcessor(String name) {
dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
}
}
// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
public SpecificContext() {
// the SpecificContext supports the SpecificProcessor
registerProcessor("specific", new SpecificProcessor());
}
}
The problem is that I need to declare a generic Dispatcher in the base Context class, but I want the type-variable to refer to the specific derived type for each Context sub-type. I can't see a way to do this without duplicating some code in each Context subclass (specifically, the construction of the Dispatcher and the registerProcessor method). Here's what I think I really want:
Dispatcher<MyRealClass> dispatcher = new Dispatcher<MyRealClass>();
Is there a way to declare the generic type of an object with the type of the SUBCLASS of the declaring class?
Yes, I can address this problem with a little bit of low-risk casting, so this is mostly an academic question... But I'd love to find a solution that just works top-to-bottom! Can you help? How would you approach this architecture?
UPDATE:
Here's the full source, updated to incorporate Andrzej Doyle's suggestion to use <C extends Context<C>>
; it still doesn't work, because Context<C> != C
:
class Context<C extends Context<C>> {
private final Dispatcher<C> dispatcher = new Dispatcher<C>();
public Context() {
// every context supports the BasicProcessor
registerProcessor("basic", new BasicProcessor());
}
protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
dispatcher.registerProcessor(name, processor);
}
public void runProcessor(String name) {
dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
}
}
// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
public SpecificContext() {
// the SpecificContext supports the SpecificProcessor
registerProcessor("specific", new SpecificProcessor());
}
}
abstract class AbstractProcessor<C extends Context<C>> {
public abstract void process(C context);
}
class BasicProcessor extends AbstractProcessor {
@Override
public void process(Context context) {
// ... //
}
}
class SpecificProcessor extends AbstractProcessor<SpecificContext> {
@Override
public void process(SpecificContext context) {
// ... //
}
}
class Dispatcher<C extends Context<C>> {
Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();
public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
processorMap.put(name, processor);
}
public void dispatch(String name, C context) {
processorMap.get(name).process(context);
}
}