PixelRifts
PixelRifts

Reputation: 143

Why is my isAnnotationPresent method not working eventhough the Annotation has a RetentionPolicy.RUNTIME?

I am trying to implement an Annotation based Event System for my OpenGL Game Engine. I apply the @EventListener Annotation on the method which I want to be called like this:

@EventListener(type = Type.COLLISION)
public void OnCollision(CollisionEvent data)
{
    System.out.println("HI");
}

The class in which this method sits implements an Empty Interface:

public class Sprite implements EventHandler

The EventDispatcher class:

public class EventDispatcher
{
private static List<EventHandler> registered = new ArrayList<EventHandler>();

public static void register(EventHandler EventHandler)
{
    if (!registered.contains(EventHandler))
    {
        registered.add(EventHandler);
    }
}

public static void unregister(EventHandler EventHandler)
{
    if (registered.contains(EventHandler))
    {
        registered.remove(EventHandler);
    }
}

public static List<EventHandler> getRegistered()
{
    return registered;
}

public static void dispatch(final Event event)
{
    new Thread()
    {
        @Override
        public void run()
        {
            call(event);
        }
    }.start();
}

private static void call(final Event event)
{
    for (EventHandler registered : getRegistered())
    {
        Method[] methods = registered.getClass().getMethods();

        for (int i = 0; i < methods.length; i++)
        {
            System.out.println("Annotation Being Checked");
            if (methods[i].isAnnotationPresent(EventListener.class))
            {
                System.out.println("Has Annotation");
                Class<?>[] methodParams = methods[i].getParameterTypes();
                if (methodParams.length < 1)
                {
                    continue;
                }
                if (!event.getClass().getSimpleName().equals(methodParams[0].getSimpleName()))
                {
                    continue;
                }
                try
                {
                    methods[i].invoke(registered.getClass().newInstance(), event);
                } catch (Exception exception)
                {
                    System.err.println(exception);
                }
            } else System.out.println("No Annotation");
        }
    }
}
}

But when I run the program, It always prints out

Annotation Being Checked
No Annotation

multiple Times.

Can someone help? If more information is needed, please ask and I will edit the Question.

Upvotes: 1

Views: 439

Answers (1)

dpr
dpr

Reputation: 10964

I setup a project based on your example and it's working fine. You will however see some "No Annotation" messages as your code evaluates all methods of the Sprite event handler. Even if you don't implement any additional methods other than OnCollision each class will inherit default methods from Object such as equals, hashCode or toString.

Test class:

public class SpriteTest {

    public static void main(String[] args) {
        EventDispatcher.register(new Sprite());

        CollisionEvent collisionEvent = new CollisionEvent();
        EventDispatcher.dispatch(collisionEvent);
    }
}



Apart from that there are some obvious flaws in your code:

  • Don't use stateful static members (EventDispatcher.registered) unless you know what you're doing and are aware of the multithreading aspects that come with it
  • You store instances of EventHandler but only use the class information and create a new instance on the fly - why not register the class instead of an instance directly
  • You fork new Threads for each to be dispatched event. This is very bad practice as thread creation is a costly operation. Use a thread pool instead and submit runnables or callables
  • You check if the class' simple names match to see if a handler method is applicable. This will break when using inheritance and should be replaced by Class.isAssignableFrom
  • In general usage of annotations here is questionable. You're probably better off using dedicated interfaces for the different event types. Instead of a generic EventHandler there could be a CollisionEventHandler and so on...

Rough implementation idea

public interface CollisionEventHandler extends EventHandler {
  void onCollision(CollisionEvent event);  
}

public class Sprite implements CollisionEventHandler {
  public void onCollision(CollisionEvent data) {
    System.out.println("HI");
  }
}

public class EventDispatcher {
  ...

  static void call(final CollisionEvent event) {
    getRegistered().stream()
          .filter(handler -> handler instanceof CollisionEventHandler)
          .map(handler -> (CollisionEventHandler) handler)
          .forEach(handler -> handler.onCollision(event));
  }
}

To handle different types of events you will need different call/dispatch methods. Maybe you can use the Visitor pattern (though I'm not a fan of it).

Upvotes: 1

Related Questions