package org.intellimate.izou.events;
import org.intellimate.izou.util.IzouModule;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.identification.IdentificationManager;
import org.intellimate.izou.identification.IdentificationManagerM;
import org.intellimate.izou.identification.IllegalIDException;
import org.intellimate.izou.main.Main;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
/**
* This class is used to manage local events.
*/
public class LocalEventManager extends IzouModule implements Runnable {
//here are all the Instances which fire events stored
private final ConcurrentHashMap<Identification, EventCaller> callers = new ConcurrentHashMap<>();
//the queue where all the Events are stored
final BlockingQueue<EventModel> events = new LinkedBlockingQueue<>(1);
//if false, run() will stop
private boolean stop = false;
private final EventCallable eventCallable;
public LocalEventManager(Main main) {
super(main);
IdentificationManagerM identificationManager = IdentificationManager.getInstance();
identificationManager.registerIdentification(this);
Optional<EventCallable> eventCallable = identificationManager.getIdentification(this)
.flatMap(id -> {
try {
return getMain().getEventDistributor().registerEventPublisher(id);
} catch (IllegalIDException e) {
log.fatal("Illegal ID for LocalEventManager", e);
return Optional.empty();
}
});
if (!eventCallable.isPresent()) {
log.fatal("Unable to obtain EventCallable for " + getID());
System.exit(1);
this.eventCallable = null;
} else {
this.eventCallable = eventCallable.get();
}
}
/**
* Registers with the EventManager to fire an event.
*
* Note: the same Event can be fired from multiple sources.
* Method is thread-safe.
*
* @param identification the Identification of the the instance
* @return an Optional, empty if already registered
* @throws IllegalIDException not yet implemented
*/
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"})
public Optional<EventCallable> registerCaller(Identification identification) throws IllegalIDException {
if(identification == null ||
callers.containsKey(identification)) return Optional.empty();
EventCaller eventCaller = new EventCaller(events);
callers.put(identification, eventCaller);
return Optional.of(eventCaller);
}
/**
* Unregister with the EventManager.
*
* Method is thread-safe.
*
* @param identification the Identification of the the instance
*/
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
public void unregisterCaller(Identification identification) {
if(!callers.containsKey(identification)) return;
callers.get(identification).localEvents = null;
callers.remove(identification);
}
/**
* This method fires an Event
*
* @param event the fired Event
* @throws IllegalIDException not yet implemented
* @throws org.intellimate.izou.events.MultipleEventsException if there is currently another event getting processed
*/
public void fireEvent(EventModel event) throws IllegalIDException, org.intellimate.izou.events.MultipleEventsException {
if(events == null) return;
if(events.isEmpty()) {
events.add(event);
} else {
throw new org.intellimate.izou.events.MultipleEventsException();
}
}
@Override
public void run() {
stop = false;
while (!stop) {
EventModel event;
try {
event = events.take();
if (!event.getSource().isCreatedFromInstance()) {
error("event: " + event + "has invalid source");
continue;
}
try {
eventCallable.fire(event);
} catch (org.intellimate.izou.events.MultipleEventsException e) {
log.error("unable to fire Event", e);
}
} catch (InterruptedException e) {
log.warn(e);
}
}
}
/**
* Should stop the EventManager.
*
* The method run() is a while-loop that repeats itself as long as a variable isn't true. This sets the variable true
* but does NOT interrupt the execution! If its not processing, it is waiting for an event, so this Thread still may
* not stop without interruption.
*/
@SuppressWarnings("UnusedDeclaration")
public void stop() {
stop = true;
}
/**
* Class used to fire events.
*
* To fire events a class must register with registerCaller, then this class will be returned.
* Use fire() to fire the event;
*/
@SuppressWarnings("SameParameterValue")
public final class EventCaller implements EventCallable {
private BlockingQueue<EventModel> localEvents;
//private, so that this class can only constructed by EventManager
private EventCaller(BlockingQueue<EventModel> events) {
this.localEvents = events;
}
/**
* This method is used to fire the event.
*
* @throws org.intellimate.izou.events.MultipleEventsException an Exception will be thrown if there are currently other events fired
*/
public void fire(EventModel event) throws org.intellimate.izou.events.MultipleEventsException {
if(events.isEmpty()) {
localEvents.add(event);
} else {
throw new org.intellimate.izou.events.MultipleEventsException();
}
}
}
/**
* Exception thrown if there are multiple Events fired at the same time.
*/
@SuppressWarnings("WeakerAccess")
@Deprecated
public static class MultipleEventsException extends Exception {
public MultipleEventsException() {
super("Multiple Events fired at the same time");
}
}
}