2 * Reactor - Engine.java - Copyright © 2013 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.reactor.engine;
22 import java.util.SortedMap;
23 import java.util.concurrent.TimeUnit;
25 import net.pterodactylus.reactor.Filter;
26 import net.pterodactylus.reactor.Query;
27 import net.pterodactylus.reactor.Reaction;
28 import net.pterodactylus.reactor.Trigger;
29 import net.pterodactylus.reactor.states.AbstractState;
30 import net.pterodactylus.reactor.states.FailedState;
32 import org.apache.log4j.Logger;
34 import com.google.common.collect.Maps;
35 import com.google.common.collect.Sets;
36 import com.google.common.util.concurrent.AbstractExecutionThreadService;
37 import com.google.common.util.concurrent.Uninterruptibles;
40 * Reactor main engine.
42 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
44 public class Engine extends AbstractExecutionThreadService {
47 private static final Logger logger = Logger.getLogger(Engine.class);
49 /** All defined reactions. */
50 private final Set<Reaction> reactions = Sets.newHashSet();
52 /** Reaction states. */
53 private final Map<Reaction, ReactionExecution> reactionExecutions = Maps.newHashMap();
60 * Adds the given reaction to this engine.
63 * The reaction to add to this engine
65 @SuppressWarnings("synthetic-access")
66 public void addReaction(Reaction reaction) {
67 reactions.add(reaction);
68 reactionExecutions.put(reaction, new ReactionExecution());
72 // ABSTRACTSERVICE METHODS
82 /* delay if we have no reactions. */
83 if (reactions.isEmpty()) {
84 logger.trace("Sleeping for 1 second while no Reactions available.");
85 Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
89 /* find next reaction. */
90 SortedMap<Long, Reaction> nextReactions = Maps.newTreeMap();
91 for (Reaction reaction : reactions) {
92 ReactionExecution reactionExecution = reactionExecutions.get(reaction);
93 nextReactions.put(reactionExecution.lastExecutionTime() + reaction.updateInterval(), reaction);
95 Reaction nextReaction = nextReactions.get(nextReactions.firstKey());
96 ReactionExecution reactionExecution = reactionExecutions.get(nextReaction);
97 logger.debug(String.format("Next Reaction: %s.", nextReaction));
99 /* wait until the next reaction has to run. */
100 while (isRunning()) {
101 long waitTime = (reactionExecution.lastExecutionTime() + nextReaction.updateInterval()) - System.currentTimeMillis();
102 logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
107 logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
108 TimeUnit.MILLISECONDS.sleep(waitTime);
109 } catch (InterruptedException ie1) {
114 /* are we still running? */
120 reactionExecution.setLastExecutionTime(System.currentTimeMillis());
121 Query query = nextReaction.query();
122 net.pterodactylus.reactor.State state;
124 logger.debug("Querying system...");
125 state = query.state();
127 state = FailedState.INSTANCE;
129 logger.debug("System queried.");
130 } catch (Throwable t1) {
131 logger.warn("Querying system failed!", t1);
132 state = new AbstractState(t1) {
133 /* no further state. */
136 logger.debug(String.format("State is %s.", state));
138 /* convert states. */
139 for (Filter filter : nextReaction.filters()) {
140 if (state.success()) {
141 net.pterodactylus.reactor.State newState = filter.filter(state);
142 logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
146 if (state.success()) {
147 reactionExecution.addState(state);
150 /* only run trigger if we have collected two states. */
151 Trigger trigger = nextReaction.trigger();
152 boolean triggerHit = false;
153 if ((reactionExecution.previousState() != null) && state.success()) {
154 logger.debug("Checking Trigger for changes...");
155 triggerHit = trigger.triggers(reactionExecution.currentState(), reactionExecution.previousState());
158 /* run action if trigger was hit. */
159 logger.debug(String.format("Trigger was hit: %s.", triggerHit));
161 logger.info("Executing Action...");
162 nextReaction.action().execute(trigger.output());
169 * Stores execution states of a {@link Reaction}.
171 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
173 private static class ReactionExecution {
175 /** The time the reaction was last executed. */
176 private long lastExecutionTime;
178 /** The previous state of the reaction. */
179 private net.pterodactylus.reactor.State previousState;
181 /** The current state of the reaction. */
182 private net.pterodactylus.reactor.State currentState;
189 * Returns the time the reaction was last executed. If the reaction was
190 * not yet executed, this method returns {@code 0}.
192 * @return The last execution time of the reaction (in milliseconds
193 * since Jan 1, 1970 UTC)
195 public long lastExecutionTime() {
196 return lastExecutionTime;
200 * Returns the current state of the reaction. If the reaction was not
201 * yet executed, this method returns {@code null}.
203 * @return The current state of the reaction
205 public net.pterodactylus.reactor.State currentState() {
210 * Returns the previous state of the reaction. If the reaction was not
211 * yet executed at least twice, this method returns {@code null}.
213 * @return The previous state of the reaction
215 public net.pterodactylus.reactor.State previousState() {
216 return previousState;
220 * Sets the last execution time of the reaction.
222 * @param lastExecutionTime
223 * The last execution time of the reaction (in milliseconds
224 * since Jan 1, 1970 UTC)
225 * @return This execution
227 public ReactionExecution setLastExecutionTime(long lastExecutionTime) {
228 this.lastExecutionTime = lastExecutionTime;
237 * Adds the given state as current state and moves the current state
238 * into the previous state.
241 * The new current state
242 * @return This execution
244 public ReactionExecution addState(net.pterodactylus.reactor.State state) {
245 previousState = currentState;
246 currentState = state;