2

I'm writing a IRC bot that responds to commands like !time, !sayHello and similar. One of the challenges I've had is that parsing these commands ended up with a long if/else bundle. To get around this problem of a big if/else tumble, I'm trying to use annotations to add extra commands into the bot, this makes the code look far cleaner and is easier to maintain and extend.

It looks like this;

public class ExampleChatPlugin extends Plugin {
    @CommandMessage(command="!time")
    public void handleTime(Bot bot, String channel, Command fullMessage) {
        bot.sendMessage(channel, "I don't know the time :(");
    }

    @CommandMessage(command="!sayHello")
    public void handleSayHello(Bot bot, String channel, Command fullMessage) {
        bot.sendMessage(channel, "Oh, hi there.");
    }

    // etc...
}

So! I have message handling method in the superclass Plugin that works out which method to call when a message is received. This method is here;

public void callCommandMessages(String channel, String sender, String input) {
    Command command = new Command(input);

    for (Method m : this.getMethodsWithAnnotation()) {
        String keyword = m.getAnnotation(CommandMessage.class).keyword();

        if (keyword.isEmpty()) {
            keyword = m.getName();
        }

        if (command.supportsKeyword(keyword)) {
            if (m.getGenericParameterTypes().length < 4) {
                MessagePlugin.LOG.debug("InputSelector method found for " + input + ", but this method needs to 4 arguments");
            } else {
                try {
                    m.invoke(this, channel, sender, command);
                    return;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    System.out.println("Could not find: " + input);
}

So, my problem is that this method checks if the @CommandMessage method takes 4 parameters, but this isn't very strict and most importantly it's only detected at runtime. It sucks!

Is there I way that I get get an annotated method to be forced to have the parameters Bot bot, String channel, String sender, Command command

I know the most obvious way would be an interface as that is precisely what they were designed for, but this means having a class for every single @CommandMessage and that really sucks too.

3
  • Can you give an example of the big if/else bundles you dislike seeing? I would have thought a solution involving annotations would be rather over-baked. Commented Dec 20, 2013 at 14:46
  • This sounds like a job for an annotation processor (although they're pretty fiddly to write). Commented Dec 20, 2013 at 14:47
  • You can fail on startup if your annotated methods don't match your argument list. Commented Dec 20, 2013 at 14:51

2 Answers 2

1

I'd recommend a soltution like this:

public interface PluginMethod {
    void execute(Bot bot, Channel channel, Sender sender, Command command);
    String getCommand();
}

public interface Plugin {
    List<PluginMethod> getPluginMethods();
}

Not so fancy as with annotaitons but you enforce that each method uses the 4-Attribute-Interface.

Sign up to request clarification or add additional context in comments.

2 Comments

This would also allow a caching between the command and its implementing instance using a Map. And additionally you can use the ServiceLoader mechanism to discover custom plugins.
Yup, this would be a totally standard way of solving the problem, but I'm concerned about a massive sprawl of classes - talking about in the region of 100-120 commands at the moment. There are also some commands like !quidAdd and !quizStart that are directly related, it would be nice to keep these in the same class so they can share functionality.
0

Is there I way that I get get an annotated method to be forced to have the parameters Bot bot, String channel, String sender, Command command

I can't immediately think of actually enforcing this in a standard way. I have however "solved" similar things by writing unit tests that check these kind of things by using reflection and package scanning.

  • Get all classes in a predefined java package
  • Iterate all the methods
  • If @CommandMessage annotation is present check if the method signature is correct

However this depends on a continuous integration workflow, specifically that a build of your code only succeeds when all tests run successfully.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.