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.Map;
27 import java.util.Stack;
28
29 import org.apache.mina.statemachine.context.StateContext;
30 import org.apache.mina.statemachine.event.Event;
31 import org.apache.mina.statemachine.event.UnhandledEventException;
32 import org.apache.mina.statemachine.transition.Transition;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36
37
38
39
40
41
42
43
44
45
46
47 public class StateMachine {
48 private static final Logger log = LoggerFactory.getLogger(StateMachine.class);
49 private static final String CALL_STACK = StateMachine.class.getName() + ".callStack";
50 private final State startState;
51 private final Map<String, State> states;
52
53 private final ThreadLocal<Boolean> processingThreadLocal = new ThreadLocal<Boolean>() {
54 protected Boolean initialValue() {
55 return Boolean.FALSE;
56 }
57 };
58
59 private final ThreadLocal<LinkedList<Event>> eventQueueThreadLocal = new ThreadLocal<LinkedList<Event>>() {
60 protected LinkedList<Event> initialValue() {
61 return new LinkedList<Event>();
62 }
63 };
64
65
66
67
68
69
70
71
72 public StateMachine(State[] states, String startStateId) {
73 this.states = new HashMap<String, State>();
74 for (State s : states) {
75 this.states.put(s.getId(), s);
76 }
77 this.startState = getState(startStateId);
78 }
79
80
81
82
83
84
85
86
87 public StateMachine(Collection<State> states, String startStateId) {
88 this(states.toArray(new State[0]), startStateId);
89 }
90
91
92
93
94
95
96
97
98 public State getState(String id) throws NoSuchStateException {
99 State state = states.get(id);
100 if (state == null) {
101 throw new NoSuchStateException(id);
102 }
103 return state;
104 }
105
106
107
108
109
110
111
112 public Collection<State> getStates() {
113 return Collections.unmodifiableCollection(states.values());
114 }
115
116
117
118
119
120
121
122
123
124
125
126 public void handle(Event event) {
127 StateContext context = event.getContext();
128
129 synchronized (context) {
130 LinkedList<Event> eventQueue = eventQueueThreadLocal.get();
131 eventQueue.addLast(event);
132
133 if (processingThreadLocal.get()) {
134
135
136
137
138 if (log.isDebugEnabled()) {
139 log.debug("State machine called recursively. Queuing event " + event
140 + " 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 (log.isDebugEnabled()) {
170 log.debug("Trying transition " + t);
171 }
172
173 try {
174 if (t.execute(event)) {
175 if (log.isDebugEnabled()) {
176 log.debug("Transition " + t + " executed successfully.");
177 }
178 setCurrentState(context, t.getNextState());
179
180 return;
181 }
182 } catch (BreakAndContinueException bace) {
183 if (log.isDebugEnabled()) {
184 log.debug("BreakAndContinueException thrown in "
185 + "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 (log.isDebugEnabled()) {
193 log.debug("BreakAndGotoException thrown in "
194 + "transition " + t + ". Moving to state "
195 + newState.getId() + " now.");
196 }
197 setCurrentState(context, newState);
198 handle(newState, event);
199 } else {
200 if (log.isDebugEnabled()) {
201 log.debug("BreakAndGotoException thrown in "
202 + "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
213 ? getState(bace.getReturnToStateId())
214 : context.getCurrentState();
215 callStack.push(returnTo);
216
217 if (bace.isNow()) {
218 if (log.isDebugEnabled()) {
219 log.debug("BreakAndCallException thrown in "
220 + "transition " + t + ". Moving to state "
221 + newState.getId() + " now.");
222 }
223 setCurrentState(context, newState);
224 handle(newState, event);
225 } else {
226 if (log.isDebugEnabled()) {
227 log.debug("BreakAndCallException thrown in "
228 + "transition " + t + ". Moving to state "
229 + newState.getId() + " next.");
230 }
231 setCurrentState(context, newState);
232 }
233 return;
234 } catch (BreakAndReturnException bare) {
235 Stack<State> callStack = getCallStack(context);
236 State newState = callStack.pop();
237
238 if (bare.isNow()) {
239 if (log.isDebugEnabled()) {
240 log.debug("BreakAndReturnException thrown in "
241 + "transition " + t + ". Moving to state "
242 + newState.getId() + " now.");
243 }
244 setCurrentState(context, newState);
245 handle(newState, event);
246 } else {
247 if (log.isDebugEnabled()) {
248 log.debug("BreakAndReturnException thrown in "
249 + "transition " + t + ". Moving to state "
250 + newState.getId() + " next.");
251 }
252 setCurrentState(context, newState);
253 }
254 return;
255 }
256 }
257
258
259
260
261
262
263 if (state.getParent() != null) {
264 handle(state.getParent(), event);
265 } else {
266 throw new UnhandledEventException(event);
267 }
268 }
269
270 private Stack<State> getCallStack(StateContext context) {
271 @SuppressWarnings("unchecked")
272 Stack<State> callStack = (Stack<State>) context.getAttribute(CALL_STACK);
273 if (callStack == null) {
274 callStack = new Stack<State>();
275 context.setAttribute(CALL_STACK, callStack);
276 }
277 return callStack;
278 }
279
280 private void setCurrentState(StateContext context, State newState) {
281 if (newState != null) {
282 if (log.isDebugEnabled()) {
283 if (newState != context.getCurrentState()) {
284 log.debug("Leaving state " + context.getCurrentState().getId());
285 log.debug("Entering state " + newState.getId());
286 }
287 }
288 context.setCurrentState(newState);
289 }
290 }
291
292 }