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;
20 import java.util.HashMap;
22 import java.util.SortedMap;
24 import net.pterodactylus.reactor.Filter;
25 import net.pterodactylus.reactor.Query;
26 import net.pterodactylus.reactor.Reaction;
27 import net.pterodactylus.reactor.Trigger;
28 import net.pterodactylus.reactor.states.AbstractState;
29 import net.pterodactylus.reactor.states.FailedState;
31 import org.apache.log4j.Logger;
33 import com.google.common.collect.Maps;
34 import com.google.common.util.concurrent.AbstractExecutionThreadService;
37 * Reactor main engine.
39 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
41 public class Engine extends AbstractExecutionThreadService {
44 private static final Logger logger = Logger.getLogger(Engine.class);
46 /** All defined reactions. */
47 /* synchronize on itself. */
48 private final Map<String, Reaction> reactions = new HashMap<String, Reaction>();
50 /** Reaction states. */
51 /* synchronize on reactions. */
52 private final Map<Reaction, ReactionExecution> reactionExecutions = Maps.newHashMap();
59 * Adds the given reaction to this engine.
62 * The name of the reaction
64 * The reaction to add to this engine
65 * @throws IllegalStateException
66 * if the engine already contains a {@link Reaction} with the
69 @SuppressWarnings("synthetic-access")
70 public void addReaction(String name, Reaction reaction) {
71 synchronized (reactions) {
72 if (reactions.containsKey(name)) {
73 throw new IllegalStateException(String.format("Engine already contains a Reaction named “%s!”", name));
75 reactions.put(name, reaction);
76 reactionExecutions.put(reaction, new ReactionExecution());
77 reactions.notifyAll();
82 // ABSTRACTSERVICE METHODS
92 /* delay if we have no reactions. */
93 synchronized (reactions) {
94 if (reactions.isEmpty()) {
95 logger.debug("Sleeping while no Reactions available.");
98 } catch (InterruptedException ie1) {
99 /* ignore, we’re looping anyway. */
105 /* find next reaction. */
106 SortedMap<Long, Reaction> nextReactions = Maps.newTreeMap();
107 Reaction nextReaction;
108 ReactionExecution reactionExecution;
109 synchronized (reactions) {
110 for (Reaction reaction : reactions.values()) {
111 nextReactions.put(reactionExecutions.get(reaction).lastExecutionTime() + reaction.updateInterval(), reaction);
113 nextReaction = nextReactions.get(nextReactions.firstKey());
114 reactionExecution = reactionExecutions.get(nextReaction);
116 logger.debug(String.format("Next Reaction: %s.", nextReaction));
118 /* wait until the next reaction has to run. */
119 long waitTime = (reactionExecution.lastExecutionTime() + nextReaction.updateInterval()) - System.currentTimeMillis();
120 logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
122 synchronized (reactions) {
124 logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
125 reactions.wait(waitTime);
126 } catch (InterruptedException ie1) {
131 /* re-start loop to check for new reactions. */
136 reactionExecution.setLastExecutionTime(System.currentTimeMillis());
137 Query query = nextReaction.query();
138 net.pterodactylus.reactor.State state;
140 logger.debug("Querying system...");
141 state = query.state();
143 state = FailedState.INSTANCE;
145 logger.debug("System queried.");
146 } catch (Throwable t1) {
147 logger.warn("Querying system failed!", t1);
148 state = new AbstractState(t1) {
149 /* no further state. */
152 logger.debug(String.format("State is %s.", state));
154 /* convert states. */
155 for (Filter filter : nextReaction.filters()) {
156 if (state.success()) {
157 net.pterodactylus.reactor.State newState = filter.filter(state);
158 logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
162 if (state.success()) {
163 reactionExecution.addState(state);
166 /* only run trigger if we have collected two states. */
167 Trigger trigger = nextReaction.trigger();
168 boolean triggerHit = false;
169 if ((reactionExecution.previousState() != null) && state.success()) {
170 logger.debug("Checking Trigger for changes...");
171 triggerHit = trigger.triggers(reactionExecution.currentState(), reactionExecution.previousState());
174 /* run action if trigger was hit. */
175 logger.debug(String.format("Trigger was hit: %s.", triggerHit));
177 logger.info("Executing Action...");
178 nextReaction.action().execute(trigger.output());
185 * Stores execution states of a {@link Reaction}.
187 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
189 private static class ReactionExecution {
191 /** The time the reaction was last executed. */
192 private long lastExecutionTime;
194 /** The previous state of the reaction. */
195 private net.pterodactylus.reactor.State previousState;
197 /** The current state of the reaction. */
198 private net.pterodactylus.reactor.State currentState;
205 * Returns the time the reaction was last executed. If the reaction was
206 * not yet executed, this method returns {@code 0}.
208 * @return The last execution time of the reaction (in milliseconds
209 * since Jan 1, 1970 UTC)
211 public long lastExecutionTime() {
212 return lastExecutionTime;
216 * Returns the current state of the reaction. If the reaction was not
217 * yet executed, this method returns {@code null}.
219 * @return The current state of the reaction
221 public net.pterodactylus.reactor.State currentState() {
226 * Returns the previous state of the reaction. If the reaction was not
227 * yet executed at least twice, this method returns {@code null}.
229 * @return The previous state of the reaction
231 public net.pterodactylus.reactor.State previousState() {
232 return previousState;
236 * Sets the last execution time of the reaction.
238 * @param lastExecutionTime
239 * The last execution time of the reaction (in milliseconds
240 * since Jan 1, 1970 UTC)
241 * @return This execution
243 public ReactionExecution setLastExecutionTime(long lastExecutionTime) {
244 this.lastExecutionTime = lastExecutionTime;
253 * Adds the given state as current state and moves the current state
254 * into the previous state.
257 * The new current state
258 * @return This execution
260 public ReactionExecution addState(net.pterodactylus.reactor.State state) {
261 previousState = currentState;
262 currentState = state;