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 * Removes the reaction with the given name.
85 * The name of the reaction to remove
87 public void removeReaction(String name) {
88 synchronized (reactions) {
89 if (!reactions.containsKey(name)) {
92 Reaction reaction = reactions.remove(name);
93 reactionExecutions.remove(reaction);
94 reactions.notifyAll();
99 // ABSTRACTSERVICE METHODS
107 while (isRunning()) {
109 /* delay if we have no reactions. */
110 synchronized (reactions) {
111 if (reactions.isEmpty()) {
112 logger.debug("Sleeping while no Reactions available.");
115 } catch (InterruptedException ie1) {
116 /* ignore, we’re looping anyway. */
122 /* find next reaction. */
123 SortedMap<Long, Reaction> nextReactions = Maps.newTreeMap();
124 Reaction nextReaction;
125 ReactionExecution reactionExecution;
126 synchronized (reactions) {
127 for (Reaction reaction : reactions.values()) {
128 nextReactions.put(reactionExecutions.get(reaction).lastExecutionTime() + reaction.updateInterval(), reaction);
130 nextReaction = nextReactions.get(nextReactions.firstKey());
131 reactionExecution = reactionExecutions.get(nextReaction);
133 logger.debug(String.format("Next Reaction: %s.", nextReaction));
135 /* wait until the next reaction has to run. */
136 long waitTime = (reactionExecution.lastExecutionTime() + nextReaction.updateInterval()) - System.currentTimeMillis();
137 logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
139 synchronized (reactions) {
141 logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
142 reactions.wait(waitTime);
143 } catch (InterruptedException ie1) {
148 /* re-start loop to check for new reactions. */
153 reactionExecution.setLastExecutionTime(System.currentTimeMillis());
154 Query query = nextReaction.query();
155 net.pterodactylus.reactor.State state;
157 logger.debug("Querying system...");
158 state = query.state();
160 state = FailedState.INSTANCE;
162 logger.debug("System queried.");
163 } catch (Throwable t1) {
164 logger.warn("Querying system failed!", t1);
165 state = new AbstractState(t1) {
166 /* no further state. */
169 logger.debug(String.format("State is %s.", state));
171 /* convert states. */
172 for (Filter filter : nextReaction.filters()) {
173 if (state.success()) {
174 net.pterodactylus.reactor.State newState = filter.filter(state);
175 logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
179 if (state.success()) {
180 reactionExecution.addState(state);
183 /* only run trigger if we have collected two states. */
184 Trigger trigger = nextReaction.trigger();
185 boolean triggerHit = false;
186 if ((reactionExecution.previousState() != null) && state.success()) {
187 logger.debug("Checking Trigger for changes...");
188 triggerHit = trigger.triggers(reactionExecution.currentState(), reactionExecution.previousState());
191 /* run action if trigger was hit. */
192 logger.debug(String.format("Trigger was hit: %s.", triggerHit));
194 logger.info("Executing Action...");
195 nextReaction.action().execute(trigger.output());
202 * Stores execution states of a {@link Reaction}.
204 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
206 private static class ReactionExecution {
208 /** The time the reaction was last executed. */
209 private long lastExecutionTime;
211 /** The previous state of the reaction. */
212 private net.pterodactylus.reactor.State previousState;
214 /** The current state of the reaction. */
215 private net.pterodactylus.reactor.State currentState;
222 * Returns the time the reaction was last executed. If the reaction was
223 * not yet executed, this method returns {@code 0}.
225 * @return The last execution time of the reaction (in milliseconds
226 * since Jan 1, 1970 UTC)
228 public long lastExecutionTime() {
229 return lastExecutionTime;
233 * Returns the current state of the reaction. If the reaction was not
234 * yet executed, this method returns {@code null}.
236 * @return The current state of the reaction
238 public net.pterodactylus.reactor.State currentState() {
243 * Returns the previous state of the reaction. If the reaction was not
244 * yet executed at least twice, this method returns {@code null}.
246 * @return The previous state of the reaction
248 public net.pterodactylus.reactor.State previousState() {
249 return previousState;
253 * Sets the last execution time of the reaction.
255 * @param lastExecutionTime
256 * The last execution time of the reaction (in milliseconds
257 * since Jan 1, 1970 UTC)
258 * @return This execution
260 public ReactionExecution setLastExecutionTime(long lastExecutionTime) {
261 this.lastExecutionTime = lastExecutionTime;
270 * Adds the given state as current state and moves the current state
271 * into the previous state.
274 * The new current state
275 * @return This execution
277 public ReactionExecution addState(net.pterodactylus.reactor.State state) {
278 previousState = currentState;
279 currentState = state;