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
115
116 public Collection<State> getStates() {
117 return Collections.unmodifiableCollection(states.values());
118 }
119
120
121
122
123
124
125
126
127
128
129
130 public void handle(Event event) {
131 StateContext context = event.getContext();
132
133 synchronized (context) {
134 LinkedList<Event> eventQueue = eventQueueThreadLocal.get();
135 eventQueue.addLast(event);
136
137 if (processingThreadLocal.get()) {
138
139
140
141
142 if (LOGGER.isDebugEnabled()) {
143 LOGGER.debug("State machine called recursively. Queuing event " + event + " for later processing.");
144 }
145 } else {
146 processingThreadLocal.set(true);
147 try {
148 if (context.getCurrentState() == null) {
149 context.setCurrentState(startState);
150 }
151 processEvents(eventQueue);
152 } finally {
153 processingThreadLocal.set(false);
154 }
155 }
156 }
157
158 }
159
160 private void processEvents(LinkedList<Event> eventQueue) {
161 while (!eventQueue.isEmpty()) {
162 Event event = eventQueue.removeFirst();
163 StateContext context = event.getContext();
164 handle(context.getCurrentState(), event);
165 }
166 }
167
168 private void handle(State state, Event event) {
169 StateContext context = event.getContext();
170
171 for (Transition t : state.getTransitions()) {
172 if (LOGGER.isDebugEnabled()) {
173 LOGGER.debug("Trying transition " + t);
174 }
175
176 try {
177 if (t.execute(event)) {
178 if (LOGGER.isDebugEnabled()) {
179 LOGGER.debug("Transition " + t + " executed successfully.");
180 }
181 setCurrentState(context, t.getNextState());
182
183 return;
184 }
185 } catch (BreakAndContinueException bace) {
186 if (LOGGER.isDebugEnabled()) {
187 LOGGER.debug("BreakAndContinueException thrown in " + "transition " + t
188 + ". Continuing with next transition.");
189 }
190 } catch (BreakAndGotoException bage) {
191 State newState = getState(bage.getStateId());
192
193 if (bage.isNow()) {
194 if (LOGGER.isDebugEnabled()) {
195 LOGGER.debug("BreakAndGotoException thrown in " + "transition " + t + ". Moving to state "
196 + newState.getId() + " now.");
197 }
198 setCurrentState(context, newState);
199 handle(newState, event);
200 } else {
201 if (LOGGER.isDebugEnabled()) {
202 LOGGER.debug("BreakAndGotoException thrown in " + "transition " + t + ". Moving to state "
203 + newState.getId() + " next.");
204 }
205 setCurrentState(context, newState);
206 }
207 return;
208 } catch (BreakAndCallException bace) {
209 State newState = getState(bace.getStateId());
210
211 Stack<State> callStack = getCallStack(context);
212 State returnTo = bace.getReturnToStateId() != null ? getState(bace.getReturnToStateId()) : context
213 .getCurrentState();
214 callStack.push(returnTo);
215
216 if (bace.isNow()) {
217 if (LOGGER.isDebugEnabled()) {
218 LOGGER.debug("BreakAndCallException thrown in " + "transition " + t + ". Moving to state "
219 + newState.getId() + " now.");
220 }
221 setCurrentState(context, newState);
222 handle(newState, event);
223 } else {
224 if (LOGGER.isDebugEnabled()) {
225 LOGGER.debug("BreakAndCallException thrown in " + "transition " + t + ". Moving to state "
226 + newState.getId() + " next.");
227 }
228 setCurrentState(context, newState);
229 }
230 return;
231 } catch (BreakAndReturnException bare) {
232 Stack<State> callStack = getCallStack(context);
233 State newState = callStack.pop();
234
235 if (bare.isNow()) {
236 if (LOGGER.isDebugEnabled()) {
237 LOGGER.debug("BreakAndReturnException thrown in " + "transition " + t + ". Moving to state "
238 + newState.getId() + " now.");
239 }
240 setCurrentState(context, newState);
241 handle(newState, event);
242 } else {
243 if (LOGGER.isDebugEnabled()) {
244 LOGGER.debug("BreakAndReturnException thrown in " + "transition " + t + ". Moving to state "
245 + newState.getId() + " next.");
246 }
247 setCurrentState(context, newState);
248 }
249 return;
250 }
251 }
252
253
254
255
256
257
258 if (state.getParent() != null) {
259 handle(state.getParent(), event);
260 } else {
261 throw new UnhandledEventException(event);
262 }
263 }
264
265 private Stack<State> getCallStack(StateContext context) {
266 @SuppressWarnings("unchecked")
267 Stack<State> callStack = (Stack<State>) context.getAttribute(CALL_STACK);
268 if (callStack == null) {
269 callStack = new Stack<State>();
270 context.setAttribute(CALL_STACK, callStack);
271 }
272 return callStack;
273 }
274
275 private void setCurrentState(StateContext context, State newState) {
276 if (newState != null) {
277 if (LOGGER.isDebugEnabled()) {
278 if (newState != context.getCurrentState()) {
279 LOGGER.debug("Leaving state " + context.getCurrentState().getId());
280 LOGGER.debug("Entering state " + newState.getId());
281 }
282 }
283 executeOnExits(context, context.getCurrentState());
284 executeOnEntries(context, newState);
285 context.setCurrentState(newState);
286 }
287 }
288
289 void executeOnExits(StateContext context, State state) {
290 List<SelfTransition> onExits = state.getOnExitSelfTransitions();
291 boolean isExecuted = false;
292
293 if (onExits != null)
294 for (SelfTransition selfTransition : onExits) {
295 selfTransition.execute(context, state);
296 if (LOGGER.isDebugEnabled()) {
297 isExecuted = true;
298 LOGGER.debug("Executing onEntry action for " + state.getId());
299 }
300 }
301 if (LOGGER.isDebugEnabled() && !isExecuted) {
302 LOGGER.debug("No onEntry action for " + state.getId());
303
304 }
305 }
306
307 void executeOnEntries(StateContext context, State state) {
308 List<SelfTransition> onEntries = state.getOnEntrySelfTransitions();
309 boolean isExecuted = false;
310
311 if (onEntries != null)
312 for (SelfTransition selfTransition : onEntries) {
313 selfTransition.execute(context, state);
314 if (LOGGER.isDebugEnabled()) {
315 isExecuted = true;
316 LOGGER.debug("Executing onExit action for " + state.getId());
317 }
318 }
319 if (LOGGER.isDebugEnabled() && !isExecuted) {
320 LOGGER.debug("No onEntry action for " + state.getId());
321
322 }
323
324 }
325
326 }