001package org.intellimate.izou.events;
002
003import org.intellimate.izou.util.IzouModule;
004import org.intellimate.izou.identification.Identification;
005import org.intellimate.izou.identification.IdentificationManager;
006import org.intellimate.izou.identification.IdentificationManagerM;
007import org.intellimate.izou.identification.IllegalIDException;
008import org.intellimate.izou.main.Main;
009
010import java.util.Optional;
011import java.util.concurrent.BlockingQueue;
012import java.util.concurrent.ConcurrentHashMap;
013import java.util.concurrent.LinkedBlockingQueue;
014
015/**
016 * This class is used to manage local events.
017 */
018public class LocalEventManager extends IzouModule implements Runnable {
019    //here are all the Instances which fire events stored
020    private final ConcurrentHashMap<Identification, EventCaller> callers = new ConcurrentHashMap<>();
021    //the queue where all the Events are stored
022    final BlockingQueue<EventModel> events = new LinkedBlockingQueue<>(1);
023    //if false, run() will stop
024    private boolean stop = false;
025    private final EventCallable eventCallable;
026
027    public LocalEventManager(Main main) {
028        super(main);
029        IdentificationManagerM identificationManager = IdentificationManager.getInstance();
030        identificationManager.registerIdentification(this);
031        Optional<EventCallable> eventCallable = identificationManager.getIdentification(this)
032                .flatMap(id -> {
033                    try {
034                        return getMain().getEventDistributor().registerEventPublisher(id);
035                    } catch (IllegalIDException e) {
036                        log.fatal("Illegal ID for LocalEventManager", e);
037                        return Optional.empty();
038                    }
039                });
040        if (!eventCallable.isPresent()) {
041            log.fatal("Unable to obtain EventCallable for " + getID());
042            System.exit(1);
043            this.eventCallable = null;
044        } else {
045            this.eventCallable = eventCallable.get();
046        }
047    }
048
049    /**
050     * Registers with the EventManager to fire an event.
051     *
052     * Note: the same Event can be fired from multiple sources.
053     * Method is thread-safe.
054     *
055     * @param identification the Identification of the the instance
056     * @return an Optional, empty if already registered
057     * @throws IllegalIDException not yet implemented
058     */
059    @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"})
060    public Optional<EventCallable> registerCaller(Identification identification) throws IllegalIDException {
061        if(identification == null ||
062            callers.containsKey(identification)) return Optional.empty();
063        EventCaller eventCaller = new EventCaller(events);
064        callers.put(identification, eventCaller);
065        return Optional.of(eventCaller);
066    }
067
068    /**
069     * Unregister with the EventManager.
070     *
071     * Method is thread-safe.
072     *
073     * @param identification the Identification of the the instance
074     */
075    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
076    public void unregisterCaller(Identification identification) {
077        if(!callers.containsKey(identification)) return;
078        callers.get(identification).localEvents = null;
079        callers.remove(identification);
080    }
081
082    /**
083     * This method fires an Event
084     *
085     * @param event the fired Event
086     * @throws IllegalIDException not yet implemented
087     * @throws org.intellimate.izou.events.MultipleEventsException if there is currently another event getting processed
088     */
089    public void fireEvent(EventModel event) throws IllegalIDException, org.intellimate.izou.events.MultipleEventsException {
090        if(events == null) return;
091        if(events.isEmpty()) {
092            events.add(event);
093        } else {
094            throw new org.intellimate.izou.events.MultipleEventsException();
095        }
096    }
097
098    @Override
099    public void run() {
100        stop = false;
101        while (!stop) {
102            EventModel event;
103            try {
104                event = events.take();
105                if (!event.getSource().isCreatedFromInstance()) {
106                    error("event: " + event + "has invalid source");
107                    continue;
108                }
109                try {
110                    eventCallable.fire(event);
111                } catch (org.intellimate.izou.events.MultipleEventsException e) {
112                    log.error("unable to fire Event", e);
113                }
114            } catch (InterruptedException e) {
115                log.warn(e);
116            }
117        }
118    }
119
120    /**
121     * Should stop the EventManager.
122     *
123     * The method run() is a while-loop that repeats itself as long as a variable isn't true. This sets the variable true
124     * but does NOT interrupt the execution! If its not processing, it is waiting for an event, so this Thread still may
125     * not stop without interruption.
126     */
127    @SuppressWarnings("UnusedDeclaration")
128    public void stop() {
129        stop = true;
130    }
131
132    /**
133     * Class used to fire events.
134     *
135     * To fire events a class must register with registerCaller, then this class will be returned.
136     * Use fire() to fire the event;
137     */
138    @SuppressWarnings("SameParameterValue")
139    public final class EventCaller implements EventCallable {
140        private BlockingQueue<EventModel> localEvents;
141        //private, so that this class can only constructed by EventManager
142        private EventCaller(BlockingQueue<EventModel> events) {
143            this.localEvents = events;
144        }
145
146        /**
147         * This method is used to fire the event.
148         *
149         * @throws org.intellimate.izou.events.MultipleEventsException an Exception will be thrown if there are currently other events fired
150         */
151        public void fire(EventModel event) throws org.intellimate.izou.events.MultipleEventsException {
152            if(events.isEmpty()) {
153                localEvents.add(event);
154            } else {
155                throw new org.intellimate.izou.events.MultipleEventsException();
156            }
157        }
158    }
159
160    /**
161     * Exception thrown if there are multiple Events fired at the same time.
162     */
163    @SuppressWarnings("WeakerAccess")
164    @Deprecated
165    public static class MultipleEventsException extends Exception {
166        public MultipleEventsException() {
167            super("Multiple Events fired at the same time");
168        }
169    }
170}