ProcessableGraph.java
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 numberOfStepsthrows 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 numberOfStepsthrows MaximumIterationsReachedException,
148       NoMatchingTransitionConditionFoundException {
149 
150     currentState = (ProcessableStategetStateWithID(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 = (GraphListenerit.next();
166         listener.startProcessingState(currentState, previousCondition, loop, currentStateContext);
167       }
168 
169       // check for exceeding of max loops
170       if (maximumLoops != -&& 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 = (GraphListenerit.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 = (ProcessableTransitionit.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 = (GraphListenerit.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 != -&& 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 = (ProcessableStateit.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 }