Objective
I am trying to build a MessageDispatcher that converts messages from a 3rd party API in to user defined messages and then dispatches them to a user registered listener.
The user will be expected to:
- Define an interface for each type of user message.
- Register a listener with the message dispatcher for each message type.
- Pass raw/3rd party data in to the message dispatcher.
- Handle messages passed back to listeners.
Problem Description
Unfortunately I can't seem to avoid using a raw type to achieve my desired API. I read elsewhere that there are no exceptional cases for using Raw types and they only exist in the language for backwards compatibility.
Is there a way I can change the code below to work or do I need to re-design my API?
Interfaces
The MessageDispatcher implements the following interface:
public interface MessageDispatcher {
// Register a listener for a given user defined message type.
public <T> void registerListener(
Class<T> messageClass,
MessageListener<T> listener);
// Receive data in 3rd party format, convert and dispatch.
public void onData(Data data);
}
The MessageListener interface is defined as:
public interface MessageListener<T> {
public void onMessage(T message);
}
An example user messages might look like this:
public interface MyMessage {
public String getName();
}
Registering Listeners
The user can register a listener as follows:
messageDispatcher.registerListener(MyMessage.class,
new MessageListener<MyMessage.class>() {
@Override
public void onMessage(MyMessage message) {
System.out.println("Hello " + message.getName());
}
}
A standard message dispatcher might implement the method like this:
private Map<Class<?>,MessageListener<?>> messageClassToListenerMap;
public <T> void registerListener(
Class<T> messageClass,
MessageListener<T> listener) {
messageClassToListenerMap.put(messageClass, listener);
// SNIP: Process the messageClass and extract the information needed
// for creating dynamic proxies elsewhere in a proxy factory.
}
Dispatching Messages
When a new message is received by the MessageDispatcher it creates a dynamic proxy for the object and dispatches it to an appropriate listener. But this is where my problem is:
public void onData(Data data) {
// SNIP: Use proxy factory (not shown) to get message class and
// dynamic proxy object appropriate to the 3rd party data.
Class<?> messageClass; // e.g. = MyMessage.class;
Object dynamicProxy; // e.g. = DynamicProxy for MyMessage.class;
// TODO: How to I pick the appropriate MessageListener and dispatch the
// dynamicProxy in a type safe way? See below.
}
If I try and use the type I can't dispatch the data:
// Assuming a listener has been registered for the example:
MessageListener<?> listener = messageClassToListenerMap.get(messageClass);
listener.onMessage(dynamicProxy); // ERROR: can't accept Object.
listener.onMessage(messageClass.cast(dynamicProxy); // ERROR: Wrong capture.
It makes sense, because there's no way I can know what type of data my listener accepts and what type of data I am passing it.
But if I use raw types it works fine:
// Assuming a listener has been registered for the example:
MessageListener listener = messageClassToListenerMap.get(messageClass);
listener.onMessage(dynamicProxy); // OK, provided I always pass the correct type of object.