/*******************************************************************************
* Copyright (C) 2014 Travis Ralston (turt2live)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.turt2live.antishare.events;
import com.turt2live.antishare.collections.ArrayArrayList;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* AntiShare Event Dispatcher
*
* @author turt2live
*/
public final class EventDispatcher {
private static class Listener {
Method method;
Object object;
Class<? extends Event> eventClass;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Listener)) return false;
Listener listener = (Listener) o;
if (eventClass != null ? !eventClass.equals(listener.eventClass) : listener.eventClass != null)
return false;
if (method != null ? !method.equals(listener.method) : listener.method != null) return false;
return !(object != null ? !object.equals(listener.object) : listener.object != null);
}
@Override
public int hashCode() {
int result = method != null ? method.hashCode() : 0;
result = 31 * result + (object != null ? object.hashCode() : 0);
result = 31 * result + (eventClass != null ? eventClass.hashCode() : 0);
return result;
}
}
private static ConcurrentMap<String, List<Listener>> listeners = new ConcurrentHashMap<>();
/**
* Registers an object with the dispatcher. Invalid methods are silently ignored.
* <p/>
* A "valid" method is one which has the {@link com.turt2live.antishare.events.EventListener}
* annotation as well as accepts a single argument of type {@link Event}.
*
* @param object the object to register. Null input is ignored.
*/
public static void register(Object object) {
if (object == null) return;
List<Listener> listenerList = getListeners(object);
for (Listener listener : listenerList) {
List<Listener> methods = getList(listener.eventClass);
methods.add(listener);
}
}
/**
* Removes an object from the dispatcher
*
* @param object the object to de-register. Null input is ignored.
*/
public static void deregister(Object object) {
if (object == null) return;
List<Listener> listenerList = getListeners(object);
for (Listener listener : listenerList) {
List<Listener> methods = getList(listener.eventClass);
methods.remove(listener);
}
}
private static List<Listener> getListeners(Object object) {
List<Listener> listenerList = new ArrayList<>();
Class<?> clazz = object.getClass();
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())
&& !Modifier.isStatic(method.getModifiers())
&& method.isAnnotationPresent(EventListener.class)) {
Class<?>[] paramaters = method.getParameterTypes();
if (paramaters != null && paramaters.length == 1) {
Listener testListener = new Listener();
testListener.method = method;
testListener.object = object;
if (Event.class.isAssignableFrom(paramaters[0])) {
testListener.eventClass = (Class<? extends Event>) paramaters[0];
listenerList.add(testListener);
}
}
}
}
return listenerList;
}
private static List<Listener> getList(Class<? extends Event> eventClass) {
String clazz = eventClass.getName();
if (!listeners.containsKey(clazz)) listeners.put(clazz, new CopyOnWriteArrayList<Listener>());
return listeners.get(clazz);
}
private static List<Class<? extends Event>> getEventClasses(Class<?> clazz) {
List<Class<? extends Event>> classes = new ArrayList<>();
Class<?> sup = clazz.getSuperclass();
if (Event.class.isAssignableFrom(sup)) {
if (!classes.contains(sup)) classes.add((Class<? extends Event>) sup);
List<Class<? extends Event>> more = getEventClasses(sup);
for (Class<? extends Event> clazz2 : more) {
if (!classes.contains(clazz2)) classes.add(clazz2);
}
}
return classes;
}
/**
* Dispatches an event to all listeners
*
* @param event the event to dispatch
*/
public static void dispatch(Event event) {
if (event == null) throw new IllegalArgumentException("Event is null");
List<Class<? extends Event>> classes = new ArrayArrayList<>(event.getClass(), Event.class);
// Determine all subclass events (derived/generic listeners)
classes.addAll(getEventClasses(event.getClass()));
for (Class<? extends Event> clazz : classes) {
List<Listener> listenerList = getList(clazz);
if (listenerList == null) return; // Don't fire nothing
for (Listener listener : listenerList) {
try {
listener.method.invoke(listener.object, event);
} catch (Exception e) {
throw new RuntimeException("Cannot access method: " + e.getMessage());
}
}
}
}
}