1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.statemachine;
21
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Stack;
29
30 import org.apache.mina.statemachine.context.StateContext;
31 import org.apache.mina.statemachine.event.Event;
32 import org.apache.mina.statemachine.event.UnhandledEventException;
33 import org.apache.mina.statemachine.transition.SelfTransition;
34 import org.apache.mina.statemachine.transition.Transition;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38
39
40
41
42
43
44
45
46
47
48 public class StateMachine {
49 private static final Logger LOGGER = LoggerFactory.getLogger(StateMachine.class);
50 private static final String CALL_STACK = StateMachine.class.getName() + ".callStack";
51 private final State startState;
52 private final Map<String, State> states;
53
54 private final ThreadLocal<Boolean> processingThreadLocal = new ThreadLocal<Boolean>() {
55 protected Boolean initialValue() {
56 return Boolean.FALSE;
57 }
58 };
59
60 private final ThreadLocal<LinkedList<Event>> eventQueueThreadLocal = new ThreadLocal<LinkedList<Event>>() {
61 protected LinkedList<Event> initialValue() {
62 return new LinkedList<Event>();
63 }
64 };
65
66
67
68
69
70
71
72
73 public StateMachine(State[] states, String startStateId) {
74 this.states = new HashMap<String, State>();
75 for (State s : states) {
76 this.states.put(s.getId(), s);
77 }
78 this.startState = getState(startStateId);
79 }
80
81
82
83
84
85
86
87
88 public StateMachine(Collection<State> states, String startStateId) {
89 this(states.toArray(new State[0]), startStateId);
90 }
91
92
93
94
95
96
97
98
99 public State getState(String id) throws NoSuchStateException {
100 State state = states.get(id);
101 if (state == null) {
102 throw new NoSuchStateException(id);
103 }
104 return state;
105 }
106
107
108
109
110
111
112
113 public Collection<State> getStates() {
114 return Collections.unmodifiableCollection(states.values());
115 }
116
117
118
119
120
121
122
123
124
125
126
127 public void handle(Event event) {
128 StateContext context = event.getContext();
129
130 synchronized (context) {
131 LinkedList<Event> eventQueue = eventQueueThreadLocal.get();
132 eventQueue.addLast(event);
133
134 if (processingThreadLocal.get()) {
135
136
137
138
139 if (LOGGER.isDebugEnabled()) {
140 LOGGER.debug("State machine called recursively. Queuing event " + event + " for later processing.");
141 }
142 } else {
143 processingThreadLocal.set(true);
144 try {
145 if (context.getCurrentState() == null) {
146 context.setCurrentState(startState);
147 }
148 processEvents(eventQueue);
149 } finally {
150 processingThreadLocal.set(false);
151 }
152 }
153 }
154
155 }
156
157 private void processEvents(LinkedList<Event> eventQueue) {
158 while (!eventQueue.isEmpty()) {
159 Event event = eventQueue.removeFirst();
160 StateContext context = event.getContext();
161 handle(context.getCurrentState(), event);
162 }
163 }
164
165 private void handle(State state, Event event) {
166 StateContext context = event.getContext();
167
168 for (Transition t : state.getTransitions()) {
169 if (LOGGER.isDebugEnabled()) {
170 LOGGER.debug("Trying transition " + t);
171 }
172
173 try {
174 if (t.execute(event)) {
175 if (LOGGER.isDebugEnabled()) {
176 LOGGER.debug("Transition " + t + " executed successfully.");
177 }
178 setCurrentState(context, t.getNextState());
179
180 return;
181 }
182 } catch (BreakAndContinueException bace) {
183 if (LOGGER.isDebugEnabled()) {
184 LOGGER.debug("BreakAndContinueException thrown in " + "transition " + t
185 + ". Continuing with next transition.");
186 }
187 } catch (BreakAndGotoException bage) {
188 State newState = getState(bage.getStateId());
189
190 if (bage.isNow()) {
191 if (LOGGER.isDebugEnabled()) {
192 LOGGER.debug("BreakAndGotoException thrown in " + "transition " + t + ". Moving to state "
193 + newState.getId() + " now.");
194 }
195 setCurrentState(context, newState);
196 handle(newState, event);
197 } else {
198 if (LOGGER.isDebugEnabled()) {
199 LOGGER.debug("BreakAndGotoException thrown in " + "transition " + t + ". Moving to state "
200 + newState.getId() + " next.");
201 }
202 setCurrentState(context, newState);
203 }
204 return;
205 } catch (BreakAndCallException bace) {
206 State newState = getState(bace.getStateId());
207
208 Stack<State> callStack = getCallStack(context);
209 State returnTo = bace.getReturnToStateId() != null ? getState(bace.getReturnToStateId()) : context
210 .getCurrentState();
211 callStack.push(returnTo);
212
213 if (bace.isNow()) {
214 if (LOGGER.isDebugEnabled()) {
215 LOGGER.debug("BreakAndCallException thrown in " + "transition " + t + ". Moving to state "
216 + newState.getId() + " now.");
217 }
218 setCurrentState(context, newState);
219 handle(newState, event);
220 } else {
221 if (LOGGER.isDebugEnabled()) {
222 LOGGER.debug("BreakAndCallException thrown in " + "transition " + t + ". Moving to state "
223 + newState.getId() + " next.");
224 }
225 setCurrentState(context, newState);
226 }
227 return;
228 } catch (BreakAndReturnException bare) {
229 Stack<State> callStack = getCallStack(context);
230 State newState = callStack.pop();
231
232 if (bare.isNow()) {
233 if (LOGGER.isDebugEnabled()) {
234 LOGGER.debug("BreakAndReturnException thrown in " + "transition " + t + ". Moving to state "
235 + newState.getId() + " now.");
236 }
237 setCurrentState(context, newState);
238 handle(newState, event);
239 } else {
240 if (LOGGER.isDebugEnabled()) {
241 LOGGER.debug("BreakAndReturnException thrown in " + "transition " + t + ". Moving to state "
242 + newState.getId() + " next.");
243 }
244 setCurrentState(context, newState);
245 }
246 return;
247 }
248 }
249
250
251
252
253
254
255 if (state.getParent() != null) {
256 handle(state.getParent(), event);
257 } else {
258 throw new UnhandledEventException(event);
259 }
260 }
261
262 private Stack<State> getCallStack(StateContext context) {
263 @SuppressWarnings("unchecked")
264 Stack<State> callStack = (Stack<State>) context.getAttribute(CALL_STACK);
265 if (callStack == null) {
266 callStack = new Stack<State>();
267 context.setAttribute(CALL_STACK, callStack);
268 }
269 return callStack;
270 }
271
272 private void setCurrentState(StateContext context, State newState) {
273 if (newState != null) {
274 if (LOGGER.isDebugEnabled()) {
275 if (newState != context.getCurrentState()) {
276 LOGGER.debug("Leaving state " + context.getCurrentState().getId());
277 LOGGER.debug("Entering state " + newState.getId());
278 }
279 }
280 executeOnExits(context, context.getCurrentState());
281 executeOnEntries(context, newState);
282 context.setCurrentState(newState);
283 }
284 }
285
286 void executeOnExits(StateContext context, State state) {
287 List<SelfTransition> onExits = state.getOnExitSelfTransitions();
288 boolean isExecuted = false;
289
290 if (onExits != null)
291 for (SelfTransition selfTransition : onExits) {
292 selfTransition.execute(context, state);
293 if (LOGGER.isDebugEnabled()) {
294 isExecuted = true;
295 LOGGER.debug("Executing onEntry action for " + state.getId());
296 }
297 }
298 if (LOGGER.isDebugEnabled() && !isExecuted) {
299 LOGGER.debug("No onEntry action for " + state.getId());
300
301 }
302 }
303
304 void executeOnEntries(StateContext context, State state) {
305 List<SelfTransition> onEntries = state.getOnEntrySelfTransitions();
306 boolean isExecuted = false;
307
308 if (onEntries != null)
309 for (SelfTransition selfTransition : onEntries) {
310 selfTransition.execute(context, state);
311 if (LOGGER.isDebugEnabled()) {
312 isExecuted = true;
313 LOGGER.debug("Executing onExit action for " + state.getId());
314 }
315 }
316 if (LOGGER.isDebugEnabled() && !isExecuted) {
317 LOGGER.debug("No onEntry action for " + state.getId());
318
319 }
320
321 }
322
323 }