Run filters after query to convert the state.
[rhynodge.git] / src / main / java / net / pterodactylus / reactor / engine / Engine.java
1 /*
2  * Reactor - Engine.java - Copyright © 2013 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.reactor.engine;
19
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.SortedMap;
23 import java.util.concurrent.TimeUnit;
24
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
31 import org.apache.log4j.Logger;
32
33 import com.google.common.collect.Maps;
34 import com.google.common.collect.Sets;
35 import com.google.common.util.concurrent.AbstractExecutionThreadService;
36 import com.google.common.util.concurrent.Uninterruptibles;
37
38 /**
39  * Reactor main engine.
40  *
41  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
42  */
43 public class Engine extends AbstractExecutionThreadService {
44
45         /** The logger. */
46         private static final Logger logger = Logger.getLogger(Engine.class);
47
48         /** All defined reactions. */
49         private final Set<Reaction> reactions = Sets.newHashSet();
50
51         /** Reaction states. */
52         private final Map<Reaction, ReactionExecution> reactionExecutions = Maps.newHashMap();
53
54         //
55         // ACCESSORS
56         //
57
58         /**
59          * Adds the given reaction to this engine.
60          *
61          * @param reaction
62          *            The reaction to add to this engine
63          */
64         @SuppressWarnings("synthetic-access")
65         public void addReaction(Reaction reaction) {
66                 reactions.add(reaction);
67                 reactionExecutions.put(reaction, new ReactionExecution());
68         }
69
70         //
71         // ABSTRACTSERVICE METHODS
72         //
73
74         /**
75          * {@inheritDoc}
76          */
77         @Override
78         public void run() {
79                 while (isRunning()) {
80
81                         /* delay if we have no reactions. */
82                         if (reactions.isEmpty()) {
83                                 logger.trace("Sleeping for 1 second while no Reactions available.");
84                                 Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
85                                 continue;
86                         }
87
88                         /* find next reaction. */
89                         SortedMap<Long, Reaction> nextReactions = Maps.newTreeMap();
90                         for (Reaction reaction : reactions) {
91                                 ReactionExecution reactionExecution = reactionExecutions.get(reaction);
92                                 nextReactions.put(reactionExecution.lastExecutionTime() + reaction.updateInterval(), reaction);
93                         }
94                         Reaction nextReaction = nextReactions.get(nextReactions.firstKey());
95                         ReactionExecution reactionExecution = reactionExecutions.get(nextReaction);
96                         logger.debug(String.format("Next Reaction: %s.", nextReaction));
97
98                         /* wait until the next reaction has to run. */
99                         while (isRunning()) {
100                                 long waitTime = (reactionExecution.lastExecutionTime() + nextReaction.updateInterval()) - System.currentTimeMillis();
101                                 logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
102                                 if (waitTime <= 0) {
103                                         break;
104                                 }
105                                 try {
106                                         logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
107                                         TimeUnit.MILLISECONDS.sleep(waitTime);
108                                 } catch (InterruptedException ie1) {
109                                         /* we’re looping! */
110                                 }
111                         }
112
113                         /* are we still running? */
114                         if (!isRunning()) {
115                                 break;
116                         }
117
118                         /* run reaction. */
119                         reactionExecution.setLastExecutionTime(System.currentTimeMillis());
120                         Query query = nextReaction.query();
121                         net.pterodactylus.reactor.State state;
122                         try {
123                                 logger.debug("Querying system...");
124                                 state = query.state();
125                                 logger.debug("System queried.");
126                         } catch (Throwable t1) {
127                                 logger.warn("Querying system failed!", t1);
128                                 state = new AbstractState(t1) {
129                                         /* no further state. */
130                                 };
131                         }
132                         logger.debug(String.format("State is %s.", state));
133
134                         /* convert states. */
135                         for (Filter filter : nextReaction.filters()) {
136                                 net.pterodactylus.reactor.State newState = filter.filter(state);
137                                 logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
138                                 state = newState;
139                         }
140                         reactionExecution.addState(state);
141
142                         /* only run trigger if we have collected two states. */
143                         boolean triggerHit = false;
144                         if (reactionExecution.previousState() != null) {
145                                 Trigger trigger = nextReaction.trigger();
146                                 logger.debug("Checking Trigger for changes...");
147                                 triggerHit = trigger.triggers(reactionExecution.currentState(), reactionExecution.previousState());
148                         }
149
150                         /* run action if trigger was hit. */
151                         logger.debug(String.format("Trigger was hit: %s.", triggerHit));
152                         if (triggerHit) {
153                                 logger.info("Executing Action...");
154                                 nextReaction.action().execute(reactionExecution.currentState(), reactionExecution.previousState());
155                         }
156                 }
157         }
158
159         /**
160          * Stores execution states of a {@link Reaction}.
161          *
162          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
163          */
164         private static class ReactionExecution {
165
166                 /** The time the reaction was last executed. */
167                 private long lastExecutionTime;
168
169                 /** The previous state of the reaction. */
170                 private net.pterodactylus.reactor.State previousState;
171
172                 /** The current state of the reaction. */
173                 private net.pterodactylus.reactor.State currentState;
174
175                 //
176                 // ACCESSORS
177                 //
178
179                 /**
180                  * Returns the time the reaction was last executed. If the reaction was
181                  * not yet executed, this method returns {@code 0}.
182                  *
183                  * @return The last execution time of the reaction (in milliseconds
184                  *         since Jan 1, 1970 UTC)
185                  */
186                 public long lastExecutionTime() {
187                         return lastExecutionTime;
188                 }
189
190                 /**
191                  * Returns the current state of the reaction. If the reaction was not
192                  * yet executed, this method returns {@code null}.
193                  *
194                  * @return The current state of the reaction
195                  */
196                 public net.pterodactylus.reactor.State currentState() {
197                         return currentState;
198                 }
199
200                 /**
201                  * Returns the previous state of the reaction. If the reaction was not
202                  * yet executed at least twice, this method returns {@code null}.
203                  *
204                  * @return The previous state of the reaction
205                  */
206                 public net.pterodactylus.reactor.State previousState() {
207                         return previousState;
208                 }
209
210                 /**
211                  * Sets the last execution time of the reaction.
212                  *
213                  * @param lastExecutionTime
214                  *            The last execution time of the reaction (in milliseconds
215                  *            since Jan 1, 1970 UTC)
216                  * @return This execution
217                  */
218                 public ReactionExecution setLastExecutionTime(long lastExecutionTime) {
219                         this.lastExecutionTime = lastExecutionTime;
220                         return this;
221                 }
222
223                 //
224                 // ACTIONS
225                 //
226
227                 /**
228                  * Adds the given state as current state and moves the current state
229                  * into the previous state.
230                  *
231                  * @param state
232                  *            The new current state
233                  * @return This execution
234                  */
235                 public ReactionExecution addState(net.pterodactylus.reactor.State state) {
236                         previousState = currentState;
237                         currentState = state;
238                         return this;
239                 }
240
241         }
242
243 }