I'm working on an event manager, and I am wanting it to be a tool developers use. It is lightweight and it uses annotations to register events.
I've tried to set up synchronization
, and I'd like to ask if I'm doing it right so far. It's on GitHub.
The main class that I would worry about for synchronization would be the EventManager
class:
// JavaDocs at GitHub Repo (if needed)
public class EventManager {
private static volatile Logger logger = Logger.getLogger("EGEventManager");
private static volatile List<Class<? extends Event>> eventClasses = new ArrayList<>();
private static volatile List<EventListener> registeredListeners = new ArrayList<>();
...
public static synchronized boolean registerEventClass(Class<? extends Event> event) {
if (!eventClasses.contains(event)) {
eventClasses.add(event);
return true;
}
return false;
}
...
public static synchronized boolean unregisterEventClass(Class<? extends Event> event) {
if (eventClasses.contains(event)) {
eventClasses.remove(event);
return true;
}
return false;
}
...
public static synchronized boolean isEventClassRegistered(Class<? extends Event> event) {
return eventClasses.contains(event);
}
...
public static synchronized List<RegisteredEvent> registerEventListener(EventListener listener) {
List<RegisteredEvent> newlyRegistered = null;
if (!registeredListeners.contains(listener)) {
newlyRegistered = registerEventHandlers(listener);
}
return newlyRegistered;
}
@SuppressWarnings("unchecked")
private static synchronized List<RegisteredEvent> registerEventHandlers(EventListener listener) {
List<RegisteredEvent> newlyRegistered = new ArrayList<>();
try {
Class<? extends EventListener> eventListenerClass = listener.getClass();
Method[] classMethods = eventListenerClass.getDeclaredMethods();
for (int i = 0; i < classMethods.length; i++) {
Method method = classMethods[i];
if (method.getParameterCount() != 1) continue;
EventHandler[] methodAnnotations = method.getDeclaredAnnotationsByType(EventHandler.class);
if (methodAnnotations.length == 0) continue;
EventHandler eventHandlerAnnotation = methodAnnotations[0];
EventPriority priority = eventHandlerAnnotation.value();
Class<? extends Event> eventClass = (Class<? extends Event>) method.getParameterTypes()[0];
PrioritizedEvents.addRegisteredEvent(new RegisteredEvent(listener, method, eventClass, priority));
}
} catch (Exception e) {
e.printStackTrace();
}
return newlyRegistered;
}
...
public static synchronized boolean unregisterEventListener(EventListener listener) {
if (registeredListeners.contains(listener)) {
registeredListeners.remove(listener);
return true;
}
return false;
}
...
public static synchronized void call(EventExecutor eventExecutor, Class<? extends Event> eventClass, Object... eventArgs) {
try {
if (!eventClasses.contains(eventClass)) {
logger.warning("EventManager.call(Class<? extends Event>) cancelled: event is not contained in the registered Event classes!");
return;
}
List<RegisteredEvent> lowPriority = PrioritizedEvents.getRegisteredEvents(EventPriority.Low);
List<RegisteredEvent> normalPriority = PrioritizedEvents.getRegisteredEvents(EventPriority.Normal);
List<RegisteredEvent> highPriority = PrioritizedEvents.getRegisteredEvents(EventPriority.High);
Event eventInstance = null;
Class<?>[] constructorParameters = EventUtilities.getArrayOfClasses(eventArgs);
Constructor<?> constructor = eventClass.getDeclaredConstructor(constructorParameters);
eventInstance = (Event) constructor.newInstance(eventArgs);
for (int i = 0; i < lowPriority.size(); i++) {
RegisteredEvent registeredEvent = lowPriority.get(i);
EventListener listener = registeredEvent.getListener();
Method method = registeredEvent.getMethod();
if (registeredEvent.getEventClass() != eventClass) continue;
method.invoke(listener, eventInstance);
}
for (int i = 0; i < normalPriority.size(); i++) {
RegisteredEvent registeredEvent = normalPriority.get(i);
EventListener listener = registeredEvent.getListener();
Method method = registeredEvent.getMethod();
if (registeredEvent.getEventClass() != eventClass) continue;
method.invoke(listener, eventInstance);
}
for (int i = 0; i < highPriority.size(); i++) {
RegisteredEvent registeredEvent = highPriority.get(i);
EventListener listener = registeredEvent.getListener();
Method method = registeredEvent.getMethod();
if (registeredEvent.getEventClass() != eventClass) continue;
method.invoke(listener, eventInstance);
}
if (!eventInstance.isCancelled()) eventExecutor.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
...
static class PrioritizedEvents {
private static volatile Map<EventPriority, List<RegisteredEvent>> prioritized = new HashMap<>();
static {
EventPriority[] values = EventPriority.values();
for (int i = 0; i < values.length; i++) {
EventPriority priority = values[i];
prioritized.put(priority, new ArrayList<>());
}
}
...
public static synchronized List<RegisteredEvent> getRegisteredEvents(EventPriority priority) {
return prioritized.get(priority);
}
...
public static synchronized boolean addRegisteredEvent(RegisteredEvent registeredEvent) {
getRegisteredEvents(registeredEvent.getPriority()).add(registeredEvent);
return true;
}
}
Things in this EventManager
class like the java.util.Logger
probably doesn't need to be volatile
, but please correct me on this.
Some things like the RegisteredEvent
class, I expect that it would need to be synchronized
, but in some of the classes, I'm thinking not everything needs to be tagged with the volatile
keyword.
// Full code available at GitHub (if needed)
public class RegisteredEvent {
private EventListener listener;
private Method method;
private Class<? extends Event> eventClass;
private EventPriority priority;
...
public RegisteredEvent(EventListener listener, Method method, Class<? extends Event> eventClass, EventPriority priority) {
this.listener = listener;
this.method = method;
this.eventClass = eventClass;
this.priority = priority;
}
...
public EventListener getListener() {
return listener;
}
...
public Method getMethod() {
return method;
}
...
public Class<?> getEventClass() {
return eventClass;
}
...
public EventPriority getPriority() {
return priority;
}
}
I'm new to thread safety, so this is a bit concerning to me. Thank you for your time.