The following is some kind of similiar to the answer of @OldCurmudgeon.
The keypoint is also the listeners
field. But I declare it as this:
private final Map<Class<?>, DelegatingPacketListener> listeners
The point here is that we get rid of the list as the map value type. DelegatingPacketListener
is declared as follows:
public class DelegatingPacketListener implements PacketListener<Packet> {
private final List<PacketListener<Packet>> packetListeners;
public DelegatingPacketListener(List<? extends PacketListener<Packet>> packetListeners) {
super();
this.packetListeners = new ArrayList<PacketListener<Packet>>(packetListeners);
}
@Override
public void onOutgoingPacket(Streamer streamer, Packet packet) {
for(PacketListener<Packet> packetListener : packetListeners) {
packetListener.onOutgoingPacket(streamer, packet);
}
}
@Override
public void onIncomingPacket(Streamer streamer, Packet packet) {
for(PacketListener<Packet> packetListener : packetListeners) {
packetListener.onIncomingPacket(streamer, packet);
}
}
public List<PacketListener<Packet>> getPacketListeners() {
return Collections.unmodifiableList(packetListeners);
}
}
Now that DelegatingPacketListener
only supports listeners of type Packet
we need one more specific implementation of PacketListener
:
public class WrappingPacketListener<T extends Packet> implements PacketListener<Packet> {
private final Class<T> packetClass;
private final PacketListener<T> wrapped;
public WrappingPacketListener(Class<T> packetClass, PacketListener<T> delegate) {
super();
this.packetClass = packetClass;
this.wrapped = delegate;
}
@Override
public void onOutgoingPacket(Streamer streamer, Packet packet) {
if(packetClass.isInstance(packet)) {
T genericPacket = packetClass.cast(packet);
wrapped.onOutgoingPacket(streamer, genericPacket);
}
}
@Override
public void onIncomingPacket(Streamer streamer, Packet packet) {
if(packetClass.isInstance(packet)) {
T genericPacket = packetClass.cast(packet);
wrapped.onIncomingPacket(streamer, genericPacket);
}
}
}
Please note that the type parameter T
is not used in the implements clause. It is only for the implementation used. We will wrap every PacketListener
passed to the API in a WrappingPacketListener
. So the implementation is like this:
public List<PacketListener<Packet>> getPacketListeners(Class<?> clazz) {
return Collections.<PacketListener<Packet>>singletonList(listeners.get(clazz));
}
public <T extends Packet> void addPacketListener(Class<T> clazz, PacketListener<T> listener) {
if (listeners.containsKey(clazz) == false) {
listeners.put(clazz, new DelegatingPacketListener(Collections.singletonList(new WrappingPacketListener<T>(clazz, listener))));
return;
}
DelegatingPacketListener existing = listeners.get(clazz);
List<PacketListener<Packet>> newListeners = new ArrayList<PacketListener<Packet>>(existing.getPacketListeners());
newListeners.add(new WrappingPacketListener<T>(clazz, listener));
listeners.put(clazz, new DelegatingPacketListener(newListeners));
}
private <T extends Packet> void notifyListeners(T packet) {
List<PacketListener<Packet>> listeners = streamer.getPacketListeners(packet.getClass());
if (listeners != null) {
for (PacketListener<Packet> packetListener : listeners) {
packetListener.onIncomingPacket(streamer, packet);
}
}
}
The API has slightly changed for getPacketListeners
which doesnt use a generic type anymore.
In comparison with OldCurmudgeon's solution, this one sticks with the already existing PacketListener
interface and doesn't require an unchecked cast to be applied.
Note that the implementation is not thread safe, because of the implemention of addPacketListener
needs synchronization on the map key (as the original code in question does need too). However encapsulating the list of packet listeners in immutable DelegatingPacketListener
is probably better suited for concurrency purposes.