Persist states across reruns.
[rhynodge.git] / src / main / java / net / pterodactylus / reactor / engine / Engine.java
index 9edbdb4..74b01b1 100644 (file)
 
 package net.pterodactylus.reactor.engine;
 
+import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 import java.util.SortedMap;
-import java.util.concurrent.TimeUnit;
 
 import net.pterodactylus.reactor.Filter;
 import net.pterodactylus.reactor.Query;
@@ -28,13 +28,13 @@ import net.pterodactylus.reactor.Reaction;
 import net.pterodactylus.reactor.Trigger;
 import net.pterodactylus.reactor.states.AbstractState;
 import net.pterodactylus.reactor.states.FailedState;
+import net.pterodactylus.reactor.states.StateManager;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.log4j.Logger;
 
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.AbstractExecutionThreadService;
-import com.google.common.util.concurrent.Uninterruptibles;
 
 /**
  * Reactor main engine.
@@ -46,11 +46,12 @@ public class Engine extends AbstractExecutionThreadService {
        /** The logger. */
        private static final Logger logger = Logger.getLogger(Engine.class);
 
-       /** All defined reactions. */
-       private final Set<Reaction> reactions = Sets.newHashSet();
+       /** The state manager. */
+       private final StateManager stateManager = new StateManager("states");
 
-       /** Reaction states. */
-       private final Map<Reaction, ReactionExecution> reactionExecutions = Maps.newHashMap();
+       /** All defined reactions. */
+       /* synchronize on itself. */
+       private final Map<String, Reaction> reactions = new HashMap<String, Reaction>();
 
        //
        // ACCESSORS
@@ -59,13 +60,38 @@ public class Engine extends AbstractExecutionThreadService {
        /**
         * Adds the given reaction to this engine.
         *
+        * @param name
+        *            The name of the reaction
         * @param reaction
         *            The reaction to add to this engine
+        * @throws IllegalStateException
+        *             if the engine already contains a {@link Reaction} with the
+        *             given name
+        */
+       public void addReaction(String name, Reaction reaction) {
+               synchronized (reactions) {
+                       if (reactions.containsKey(name)) {
+                               throw new IllegalStateException(String.format("Engine already contains a Reaction named “%s!”", name));
+                       }
+                       reactions.put(name, reaction);
+                       reactions.notifyAll();
+               }
+       }
+
+       /**
+        * Removes the reaction with the given name.
+        *
+        * @param name
+        *            The name of the reaction to remove
         */
-       @SuppressWarnings("synthetic-access")
-       public void addReaction(Reaction reaction) {
-               reactions.add(reaction);
-               reactionExecutions.put(reaction, new ReactionExecution());
+       public void removeReaction(String name) {
+               synchronized (reactions) {
+                       if (!reactions.containsKey(name)) {
+                               return;
+                       }
+                       reactions.remove(name);
+                       reactions.notifyAll();
+               }
        }
 
        //
@@ -80,44 +106,53 @@ public class Engine extends AbstractExecutionThreadService {
                while (isRunning()) {
 
                        /* delay if we have no reactions. */
-                       if (reactions.isEmpty()) {
-                               logger.trace("Sleeping for 1 second while no Reactions available.");
-                               Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
-                               continue;
+                       synchronized (reactions) {
+                               if (reactions.isEmpty()) {
+                                       logger.debug("Sleeping while no Reactions available.");
+                                       try {
+                                               reactions.wait();
+                                       } catch (InterruptedException ie1) {
+                                               /* ignore, we’re looping anyway. */
+                                       }
+                                       continue;
+                               }
                        }
 
                        /* find next reaction. */
-                       SortedMap<Long, Reaction> nextReactions = Maps.newTreeMap();
-                       for (Reaction reaction : reactions) {
-                               ReactionExecution reactionExecution = reactionExecutions.get(reaction);
-                               nextReactions.put(reactionExecution.lastExecutionTime() + reaction.updateInterval(), reaction);
+                       SortedMap<Long, Pair<String, Reaction>> nextReactions = Maps.newTreeMap();
+                       String reactionName;
+                       Reaction nextReaction;
+                       synchronized (reactions) {
+                               for (Entry<String, Reaction> reactionEntry : reactions.entrySet()) {
+                                       net.pterodactylus.reactor.State state = stateManager.loadState(reactionEntry.getKey());
+                                       long stateTime = (state != null) ? state.time() : 0;
+                                       nextReactions.put(stateTime + reactionEntry.getValue().updateInterval(), Pair.of(reactionEntry.getKey(), reactionEntry.getValue()));
+                               }
+                               reactionName = nextReactions.get(nextReactions.firstKey()).getLeft();
+                               nextReaction = nextReactions.get(nextReactions.firstKey()).getRight();
                        }
-                       Reaction nextReaction = nextReactions.get(nextReactions.firstKey());
-                       ReactionExecution reactionExecution = reactionExecutions.get(nextReaction);
                        logger.debug(String.format("Next Reaction: %s.", nextReaction));
 
                        /* wait until the next reaction has to run. */
-                       while (isRunning()) {
-                               long waitTime = (reactionExecution.lastExecutionTime() + nextReaction.updateInterval()) - System.currentTimeMillis();
-                               logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
-                               if (waitTime <= 0) {
-                                       break;
+                       net.pterodactylus.reactor.State lastState = stateManager.loadState(reactionName);
+                       long lastStateTime = (lastState != null) ? lastState.time() : 0;
+                       long waitTime = (lastStateTime + nextReaction.updateInterval()) - System.currentTimeMillis();
+                       logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
+                       if (waitTime > 0) {
+                               synchronized (reactions) {
+                                       try {
+                                               logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
+                                               reactions.wait(waitTime);
+                                       } catch (InterruptedException ie1) {
+                                               /* we’re looping! */
+                                       }
                                }
-                               try {
-                                       logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
-                                       TimeUnit.MILLISECONDS.sleep(waitTime);
-                               } catch (InterruptedException ie1) {
-                                       /* we’re looping! */
-                               }
-                       }
 
-                       /* are we still running? */
-                       if (!isRunning()) {
-                               break;
+                               /* re-start loop to check for new reactions. */
+                               continue;
                        }
 
                        /* run reaction. */
-                       reactionExecution.setLastExecutionTime(System.currentTimeMillis());
                        Query query = nextReaction.query();
                        net.pterodactylus.reactor.State state;
                        try {
@@ -144,15 +179,15 @@ public class Engine extends AbstractExecutionThreadService {
                                }
                        }
                        if (state.success()) {
-                               reactionExecution.addState(state);
+                               stateManager.saveState(reactionName, state);
                        }
 
                        /* only run trigger if we have collected two states. */
                        Trigger trigger = nextReaction.trigger();
                        boolean triggerHit = false;
-                       if ((reactionExecution.previousState() != null) && state.success()) {
+                       if ((lastState != null) && state.success()) {
                                logger.debug("Checking Trigger for changes...");
-                               triggerHit = trigger.triggers(reactionExecution.currentState(), reactionExecution.previousState());
+                               triggerHit = trigger.triggers(state, lastState);
                        }
 
                        /* run action if trigger was hit. */
@@ -165,88 +200,4 @@ public class Engine extends AbstractExecutionThreadService {
                }
        }
 
-       /**
-        * Stores execution states of a {@link Reaction}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       private static class ReactionExecution {
-
-               /** The time the reaction was last executed. */
-               private long lastExecutionTime;
-
-               /** The previous state of the reaction. */
-               private net.pterodactylus.reactor.State previousState;
-
-               /** The current state of the reaction. */
-               private net.pterodactylus.reactor.State currentState;
-
-               //
-               // ACCESSORS
-               //
-
-               /**
-                * Returns the time the reaction was last executed. If the reaction was
-                * not yet executed, this method returns {@code 0}.
-                *
-                * @return The last execution time of the reaction (in milliseconds
-                *         since Jan 1, 1970 UTC)
-                */
-               public long lastExecutionTime() {
-                       return lastExecutionTime;
-               }
-
-               /**
-                * Returns the current state of the reaction. If the reaction was not
-                * yet executed, this method returns {@code null}.
-                *
-                * @return The current state of the reaction
-                */
-               public net.pterodactylus.reactor.State currentState() {
-                       return currentState;
-               }
-
-               /**
-                * Returns the previous state of the reaction. If the reaction was not
-                * yet executed at least twice, this method returns {@code null}.
-                *
-                * @return The previous state of the reaction
-                */
-               public net.pterodactylus.reactor.State previousState() {
-                       return previousState;
-               }
-
-               /**
-                * Sets the last execution time of the reaction.
-                *
-                * @param lastExecutionTime
-                *            The last execution time of the reaction (in milliseconds
-                *            since Jan 1, 1970 UTC)
-                * @return This execution
-                */
-               public ReactionExecution setLastExecutionTime(long lastExecutionTime) {
-                       this.lastExecutionTime = lastExecutionTime;
-                       return this;
-               }
-
-               //
-               // ACTIONS
-               //
-
-               /**
-                * Adds the given state as current state and moves the current state
-                * into the previous state.
-                *
-                * @param state
-                *            The new current state
-                * @return This execution
-                */
-               public ReactionExecution addState(net.pterodactylus.reactor.State state) {
-                       previousState = currentState;
-                       currentState = state;
-                       return this;
-               }
-
-       }
-
 }