Java Class.cast() and Overload
Asked Answered
N

3

3

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!

Napper answered 9/1, 2014 at 21:54 Comment(2)
The problem is that the handlePacket overload to call is decided at compile time, not dynamically.Kindly
I suspected it, so it's not possible to make it dynamically?Napper
P
3

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);

Peristome answered 9/1, 2014 at 22:5 Comment(3)
Well, that's was another option, in fact it's implemented that way allready. The reason i choose to try something different it's to have all the handles in a same class, since they're going to be small it's a little akward to be modifing packet by packet. Also if i make it that way i will need to give to the packets access to the output stream to send messages when it's needed to, wich i'll prefer to avoid. Any recomendations?Napper
This is precisely what polymorphism is FOR. Seriously, follow the advice in my answer and write a separate handle() method in each subclass. You could declare it as public void handle(OutputStream messagesOut), and just pass the OutputStream when you call it; although maybe you want a Writer, not an OutputStream if the messages are text.Peristome
Yep, i have a PacketSender wich makes all the magic. Okey then, i'll do it that way! Thanks.Napper
R
2

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.

Reina answered 9/1, 2014 at 22:8 Comment(0)
D
2

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;
        }
    };
Deceitful answered 9/1, 2014 at 22:31 Comment(3)
Can't use Java8 must be done in Java7, forgot to specify, sorry! Thanks anyway.Napper
can use anonymous class for the same effect; just more verbose.Deceitful
Okey, this seems a little more advanced i'm used to. I'll see latter in detail! Thanks! I can't upvote you since i don't have enough rep to do it =/.Napper

© 2022 - 2024 — McMap. All rights reserved.