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