Java Class.cast() and Overload

285 views Asked by At

I'm trying to code a packet listener for a little server. I'm very new to Java and this is the first time i mess around with networking. The whole idea it's recive the packet, match the packet id with it's class, pass the input stream to the constructor of the packet so it can be constructed and then give it to the packetHander, wich will have an overladed method for each packet. To achive this im using an array that maps the packets ids to the classes of each one, and using a method called decode to construct the packet. The problem it's the overloading of handlePacket it's not behaving as expected. Lets see some code.

I've the packet listener running in a thread and the run method looks like this:

public void run() {
    try {
        int packet_id;
        while ((packet_id = istream.readInt()) != -1) {
            plugin.getServer().getConsoleSender().sendMessage("[Comm] Recived packet " + packet_id);
            Packet packet = decode(packet_id, istream);

            plugin.getServer().getConsoleSender().sendMessage("[Comm] Packet is " + Class.forName(packet.getClass().getName()));
                            plugin.getServer().getConsoleSender().sendMessage("[Comm] Class is " + Packet00ReqIdentify.class.cast(packet).getClass().getName());
            plugin.getServer().getConsoleSender().sendMessage("[Comm] Class is " + Class.forName(packet.getClass().getName()).getName());

            handlePacket(packet);
        }
    } catch (IOException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

The decode and handlePacket methods looks like this:

private void handlePacket(Packet00ReqIdentify packet) throws IOException {
    plugin.getServer().getConsoleSender().sendMessage("[Comm] Got it!");
}
private void handlePacket(Packet packet) {
    plugin.getServer().getConsoleSender().sendMessage("[Comm] Woops!");
}

private Packet decode(int packet_id, PacketInputStream istream) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException, IOException {
    Class<? extends Packet> packet_class = packets_ids.get(packet_id);
    try {
        Constructor<?> packet_constructor = packet_class.getConstructor(PacketInputStream.class);
        return Class.forName(packet_class.getName()).asSubclass(Packet.class).cast(packet_constructor.newInstance(istream));
    } catch (NoSuchMethodException e) {
        return  Class.forName(packet_class.getName()).asSubclass(Packet.class).cast(packet_class.newInstance());
    }
}

packets_ids it's an array that contains the reference to the class of each packet, indexed by theirs ids:

private static ArrayList<Class<? extends Packet>> packets_ids;

It gets initialized this way:

private static void registerPacket(int id, Class<? extends Packet> oclass) {
    packets_ids.add(id, oclass);
}

static {
    packets_ids = new ArrayList<Class<? extends Packet>>();
    registerPacket(Packet00ReqIdentify.assigned_pid, Packet00ReqIdentify.class);
    registerPacket(Packet01Identify.assigned_pid, Packet01Identify.class);
    registerPacket(Packet02Heartbeat.assigned_pid, Packet02Heartbeat.class);
}

If i execute this and test it sending a packet of type 00, i get this:

17:37:49 [INFO] [Comm] Connection established to localhost:11000
17:37:49 [INFO] [Comm] Recived packet 0
17:37:49 [INFO] [Comm] Packet is class com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Class is com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Class is com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Woops!

So it means the packet00 has not been cathed by "handlePacket(Packet00ReqIdentify packet)". If I make an explicit cast to "packet" in handlePacket call it works. So the questions are:

  • Why this it's not working? When i print the class names for both i get the same.

  • How can I make it work? I been struggling with this for 6 or 7 hours for now, reading, googling, trying and seeing code from others. One simpler solution it's to make an switch using the packet id but i want something more elegant. Maybye i'm wrong from the base idea so that's why i posted the code, i'm open to suggestions and ideas of more experienced people in the subject, incluiding recomendations of material in the subject.

Thanks!

3

There are 3 answers

3
Dawood ibn Kareem On BEST ANSWER

In each of your Packet subclasses, implement a method public void handle() which does what you need to handle the packet. Either

  • Put a default implementation of handle() in Packet, or
  • Declare handle() as abstract in Packet, and make Packet an abstract class, or
  • Declare handle() in Packet and make Packet an interface.

Then replace

handlePacket(packet);

with

packet.handle();

This is polymorphism in action. It will work at run time, checking the class of the object that packet references, and calling the right version of the handle method.

If handle() needs access to the original PacketListener, then declare it as public void handle(PacketListener listener) and call it as packet.handle(this);

0
Mike B On

Here's your problem:

Packet packet = decode(packet_id, istream);

--snip--

handlePacket(packet);

Since packet is defined as a Packet it gets routed to the handlePacket(Packet packet) method even though the runtime type is a subclass of Packet.

3
ZhongYu On

You may do this (requires java8)

static Map<Class<?>, Consumer<?>> handlers = new HashMap<>();
void handlePacket(Packet packet)
{
    Consumer<Packet> handler = (Consumer<Packet>)handlers.get(packet.getClass());
    handler.accept(packet);
}

static
{
    handlers.put(Packet00ReqIdentify.class, (Packet00ReqIdentify packet)->{
        System.out.println("Packet00ReqIdentify");
    });
    handlers.put(Packet01Identify.class, (Packet01Identify packet)->{
        System.out.println("Packet01Identify");
    });
    // etc.
}

This use case "double dispatch" is frequent enough, we should make a general utility for it, like

public class DoubleDispatch<T, R>
{
    public R invoke(T obj){...}

    public <C extends T> void register(Class<C> type, Function<C,R> func){...}
}

Which can be used to solve this problem:

    DoubleDispatch<Packet,Void> dd = new DoubleDispatch<>();
    dd.register(Packet00ReqIdentify.class, packet->{
        System.out.println("Packet00ReqIdentify");
        return null;
    });
    // etc

    ...
    dd.invoke(packet);

Without lambda expression, we could use anonymous inner class, to achieve something like

    dd.new Handler<Packet00ReqIdentify>(){
        public Void handle(Packet00ReqIdentify obj) {
            System.out.println("Packet00ReqIdentify");
            return null;
        }
    };