001 /*
002 * Created on May 14, 2007
003 */
004 package com.x8ing.lsm4j.state;
005
006 import java.util.ArrayList;
007 import java.util.Iterator;
008 import java.util.LinkedList;
009 import java.util.List;
010
011 import com.x8ing.lsm4j.Action;
012 import com.x8ing.lsm4j.Condition;
013 import com.x8ing.lsm4j.GraphListener;
014 import com.x8ing.lsm4j.StateContext;
015
016 /**
017 * This class is the entry point for building a state machine. It keeps track of the graph layout and acts as controller
018 * for running it.
019 * <p>
020 * How to use this class is described in the tutorial <a href="./tutorial.html"> link </a>.
021 *
022 * @author Patrick Heusser
023 */
024 public class ProcessableGraph extends StaticGraph {
025
026 /**
027 * A list with all listeners.
028 * <p>
029 * type: {@link GraphListener}
030 */
031 private List graphListeners = new ArrayList();
032
033 /**
034 * It might be useful to specify a maximum loop amount. If more then the given numbers of transitions were executed,
035 * the processing stops with a MaximumIterationsReachedException.
036 */
037 private long maximumLoops = -1;
038
039 /**
040 * defines the size of the last visited states list.
041 */
042 private int lastVisitedStatesHistorySize = 5;
043
044 /**
045 * A list with the last visited states. The history where we came from. Index 0 means the current state.
046 * <p>
047 * element type: {@link ProcessableState}
048 */
049 private List lastVisitedStatesHistoryList = new LinkedList();
050
051 /**
052 * The last state that was processed.
053 */
054 private ProcessableState currentState = null;
055
056 /**
057 * The current context.
058 */
059 private StateContext currentStateContext = null;
060
061 public void addValidTransition(ProcessableState currentState, ProcessableState nextState, Condition condition,
062 Action transitionAction) {
063
064 ProcessableTransition processableTransition = new ProcessableTransition(currentState, nextState, condition,
065 transitionAction);
066 super.addValidTransition(processableTransition);
067
068 }
069
070 public void addValidTransition(ProcessableState currentState, ProcessableState nextState, Condition condition) {
071
072 addValidTransition(currentState, nextState, condition, null);
073
074 }
075
076 /**
077 * Continues the processing of the previously interrupted graph with a limited amount of transitions.
078 *
079 * @param numberOfSteps
080 * the number of transitions to be executed.
081 * @return the last State or null if entry point was not found.
082 * @throws NoMatchingTransitionConditionFoundException
083 * @throws MaximumIterationsReachedException
084 */
085 public ProcessableState runContinue(int numberOfSteps) throws NoMatchingTransitionConditionFoundException,
086 MaximumIterationsReachedException {
087
088 if (currentState == null) {
089 String diagnostic = printLastVisitedHistory(5);
090 handleErrorStateNotFound(0, "state to continue operation not found. diagnosticHistory=" + diagnostic);
091
092 }
093
094 return runImpl(currentState.getUniqueID(), numberOfSteps);
095 }
096
097 /**
098 * Continues the processing of the previously interrupted graph.
099 *
100 * @return the last State or null if entry point was not found.
101 * @throws NoMatchingTransitionConditionFoundException
102 * @throws MaximumIterationsReachedException
103 */
104 public ProcessableState runContinue() throws NoMatchingTransitionConditionFoundException, MaximumIterationsReachedException {
105 return runContinue(-1);
106 }
107
108 /**
109 * Starts the processing of the graph. The processing ends if an end point is found.
110 *
111 * Pleaes not that the startStateContext will be changed since it's passed by reference!
112 *
113 * @param startStateID
114 * @param startStateContext
115 * @throws NoMatchingTransitionConditionFoundException
116 * @throws MaximumIterationsReachedException
117 * @return the last State or null if entry point was not found.
118 */
119 public ProcessableState run(int startStateID, StateContext startStateContext)
120 throws NoMatchingTransitionConditionFoundException, MaximumIterationsReachedException {
121
122 return run(startStateID, startStateContext, -1);
123
124 }
125
126 /**
127 * Run the graph for a defined number of transitions.
128 *
129 * Pleae not that the startStateContext will be changed since it's passed by reference!
130 *
131 * @param startStateID
132 * @param startStateContext
133 * @param numberOfSteps
134 * the number of transitions to be executed.
135 * @throws NoMatchingTransitionConditionFoundException
136 * @throws MaximumIterationsReachedException
137 * @return the last State or null if entry point was not found.
138 */
139 public ProcessableState run(int startStateID, StateContext startStateContext, int numberOfSteps)
140 throws NoMatchingTransitionConditionFoundException, MaximumIterationsReachedException {
141
142 currentStateContext = startStateContext;
143 return runImpl(startStateID, numberOfSteps);
144
145 }
146
147 private ProcessableState runImpl(int startStateID, int numberOfSteps) throws MaximumIterationsReachedException,
148 NoMatchingTransitionConditionFoundException {
149
150 currentState = (ProcessableState) getStateWithID(startStateID);
151 if (currentState == null) {
152 return null;
153 }
154
155 Condition previousCondition = null;
156 Condition nextCondition = null;
157
158 long loop = 0;
159
160 while (true) {
161 loop++;
162
163 // notify listeners: start processing
164 for (Iterator it = graphListeners.iterator(); it.hasNext();) {
165 GraphListener listener = (GraphListener) it.next();
166 listener.startProcessingState(currentState, previousCondition, loop, currentStateContext);
167 }
168
169 // check for exceeding of max loops
170 if (maximumLoops != -1 && loop >= maximumLoops) {
171 MaximumIterationsReachedException ex = new MaximumIterationsReachedException();
172 ex.setMaximumIterationsSpecified(maximumLoops);
173 throw ex;
174 }
175
176 if (currentState == null) {
177 handleErrorStateNotFound(startStateID, "State not found");
178 }
179
180 // prepare history
181 addStateToLastVisitedHistory(currentState);
182
183 // invoke action
184 currentState.getAction().execute(currentState, currentStateContext, previousCondition, lastVisitedStatesHistoryList);
185
186 // check end state
187 if (currentState.isEndState()) {
188
189 // notify listeners: end
190 for (Iterator it = graphListeners.iterator(); it.hasNext();) {
191 GraphListener listener = (GraphListener) it.next();
192 listener.foundEndState(currentState, loop, currentStateContext);
193 }
194
195 // EXIT POINT
196 return currentState;
197
198 }
199
200 // check for a condition which is true
201 ProcessableTransition nextProcessableTransition = null;
202
203 for (Iterator it = getTransitionListForState(currentState.getUniqueID()).iterator(); it.hasNext();) {
204
205 ProcessableTransition processableTransition = (ProcessableTransition) it.next();
206
207 nextCondition = processableTransition.getCondition();
208
209 if (nextCondition.conditionTrue(currentStateContext)) {
210 // found a transition to go
211 nextProcessableTransition = processableTransition;
212 break;
213 }
214 }
215
216 if (nextProcessableTransition == null) {
217 NoMatchingTransitionConditionFoundException ex = new NoMatchingTransitionConditionFoundException();
218
219 ex.setCurrentProcessableTransition(null);
220 ex.setState(currentState);
221
222 throw ex;
223 }
224
225 ProcessableState nextState = nextProcessableTransition.getNextProcessableState();
226
227 // notify listeners: changed state
228 for (Iterator it = graphListeners.iterator(); it.hasNext();) {
229 GraphListener listener = (GraphListener) it.next();
230 listener.changedState(currentState, nextState, previousCondition, nextCondition, loop, currentStateContext);
231 }
232
233 // last: invoke an optional action on the transition
234 Action transitionAction = nextProcessableTransition.getAction();
235 if (transitionAction != null) {
236 transitionAction.execute(currentState, currentStateContext, previousCondition, lastVisitedStatesHistoryList);
237 }
238
239 // assign variables
240 currentState = nextState;
241 previousCondition = nextCondition;
242
243 // at the very end: check for limit of steps
244 if (numberOfSteps != -1 && loop >= numberOfSteps) {
245 return currentState;
246 }
247
248 }
249 }
250
251 private void addStateToLastVisitedHistory(ProcessableState currentState) {
252
253 lastVisitedStatesHistoryList.add(0, currentState);
254
255 // check max size
256 if (lastVisitedStatesHistoryList.size() > lastVisitedStatesHistorySize) {
257 lastVisitedStatesHistoryList.remove(lastVisitedStatesHistoryList.size() - 1);
258 }
259 }
260
261 public void registerGraphListener(GraphListener graphListener) {
262
263 graphListeners.add(graphListener);
264
265 }
266
267 /**
268 * @param graphListener
269 * to be removed.
270 * @return true if remove was success.
271 */
272 public boolean unregisterGraphListener(GraphListener graphListener) {
273
274 return graphListeners.remove(graphListener);
275
276 }
277
278 private void handleErrorStateNotFound(int stateID, String text) {
279 throw new RuntimeException("State not found with id=" + stateID + " info=" + text);
280 }
281
282 public long getMaximumLoops() {
283 return maximumLoops;
284 }
285
286 public void setMaximumLoops(long maximumLoops) {
287 this.maximumLoops = maximumLoops;
288 }
289
290 public int getLastVisitedStatesHistorySize() {
291 return lastVisitedStatesHistorySize;
292 }
293
294 public void setLastVisitedStatesHistorySize(int lastVisitedStatesHistorySize) {
295 this.lastVisitedStatesHistorySize = lastVisitedStatesHistorySize;
296 }
297
298 /**
299 * A list with all listeners.
300 * <p>
301 * type: {@link GraphListener}
302 */
303 public List getGraphListeners() {
304 return graphListeners;
305 }
306
307 /**
308 * A list with the last visited states. The history where we came from. Index 0 means the current state.
309 * <p>
310 * element type: {@link ProcessableState}
311 */
312 public List getLastVisitedStatesHistoryList() {
313 return lastVisitedStatesHistoryList;
314 }
315
316 private String printLastVisitedHistory(int maxEntries) {
317
318 StringBuffer ret = new StringBuffer();
319 ret.append("lastVisitedHistory { ");
320
321 if (lastVisitedStatesHistoryList != null && !lastVisitedStatesHistoryList.isEmpty()) {
322 int i = 0;
323
324 for (Iterator it = lastVisitedStatesHistoryList.iterator(); it.hasNext();) {
325 i++;
326 if (i > maxEntries) {
327 break;
328 }
329 ProcessableState state = (ProcessableState) it.next();
330 ret.append("entry #-");
331 ret.append(i);
332 ret.append(",");
333 if (state == null) {
334 ret.append("StateIsNull");
335 } else {
336 ret.append("id=");
337 ret.append(state.getUniqueID());
338 ret.append(", desc=");
339 ret.append(state.getDescription());
340 }
341 ret.append("; ");
342 }
343 }
344
345 ret.append("}");
346
347 return ret.toString();
348
349 }
350 }
|