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.
You don't need to use raw types -- just cast the wildcarded types into a type that does what you want. This kinda flies in the face of type safety. And it will give an unchecked cast warning, which you can ignore. But it proves that it's possible to not use raw types.