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