001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.statemachine;
021:
022: import java.util.Collection;
023: import java.util.Collections;
024: import java.util.HashMap;
025: import java.util.LinkedList;
026: import java.util.Map;
027: import java.util.Stack;
028:
029: import org.apache.mina.statemachine.context.StateContext;
030: import org.apache.mina.statemachine.event.Event;
031: import org.apache.mina.statemachine.event.UnhandledEventException;
032: import org.apache.mina.statemachine.transition.Transition;
033: import org.slf4j.Logger;
034: import org.slf4j.LoggerFactory;
035:
036: /**
037: * Represents a complete state machine. Contains a collection of {@link State}
038: * objects connected by {@link Transition}s. Normally you wouldn't create
039: * instances of this class directly but rather use the
040: * {@link org.apache.mina.statemachine.annotation.State} annotation to define
041: * your states and then let {@link StateMachineFactory} create a
042: * {@link StateMachine} for you.
043: *
044: * @author The Apache MINA Project (dev@mina.apache.org)
045: * @version $Rev: 588579 $, $Date: 2007-10-26 03:21:01 -0600 (Fri, 26 Oct 2007) $
046: */
047: public class StateMachine {
048: private static final Logger log = LoggerFactory
049: .getLogger(StateMachine.class);
050: private static final String CALL_STACK = StateMachine.class
051: .getName()
052: + ".callStack";
053: private final State startState;
054: private final Map<String, State> states;
055:
056: private final ThreadLocal<Boolean> processingThreadLocal = new ThreadLocal<Boolean>() {
057: protected Boolean initialValue() {
058: return Boolean.FALSE;
059: }
060: };
061:
062: private final ThreadLocal<LinkedList<Event>> eventQueueThreadLocal = new ThreadLocal<LinkedList<Event>>() {
063: protected LinkedList<Event> initialValue() {
064: return new LinkedList<Event>();
065: }
066: };
067:
068: /**
069: * Creates a new instance using the specified {@link State}s and start
070: * state.
071: *
072: * @param states the {@link State}s.
073: * @param startStateId the id of the start {@link State}.
074: */
075: public StateMachine(State[] states, String startStateId) {
076: this .states = new HashMap<String, State>();
077: for (State s : states) {
078: this .states.put(s.getId(), s);
079: }
080: this .startState = getState(startStateId);
081: }
082:
083: /**
084: * Creates a new instance using the specified {@link State}s and start
085: * state.
086: *
087: * @param states the {@link State}s.
088: * @param startStateId the id of the start {@link State}.
089: */
090: public StateMachine(Collection<State> states, String startStateId) {
091: this (states.toArray(new State[0]), startStateId);
092: }
093:
094: /**
095: * Returns the {@link State} with the specified id.
096: *
097: * @param id the id of the {@link State} to return.
098: * @return the {@link State}
099: * @throws NoSuchStateException if no matching {@link State} could be found.
100: */
101: public State getState(String id) throws NoSuchStateException {
102: State state = states.get(id);
103: if (state == null) {
104: throw new NoSuchStateException(id);
105: }
106: return state;
107: }
108:
109: /**
110: * Returns an unmodifiable {@link Collection} of all {@link State}s used by
111: * this {@link StateMachine}.
112: *
113: * @return the {@link State}s.
114: */
115: public Collection<State> getStates() {
116: return Collections.unmodifiableCollection(states.values());
117: }
118:
119: /**
120: * Processes the specified {@link Event} through this {@link StateMachine}.
121: * Normally you wouldn't call this directly but rather use
122: * {@link StateMachineProxyFactory} to create a proxy for an interface of
123: * your choice. Any method calls on the proxy will be translated into
124: * {@link Event} objects and then fed to the {@link StateMachine} by the
125: * proxy using this method.
126: *
127: * @param event the {@link Event} to be handled.
128: */
129: public void handle(Event event) {
130: StateContext context = event.getContext();
131:
132: synchronized (context) {
133: LinkedList<Event> eventQueue = eventQueueThreadLocal.get();
134: eventQueue.addLast(event);
135:
136: if (processingThreadLocal.get()) {
137: /*
138: * This thread is already processing an event. Queue this
139: * event.
140: */
141: if (log.isDebugEnabled()) {
142: log
143: .debug("State machine called recursively. Queuing event "
144: + event + " for later processing.");
145: }
146: } else {
147: processingThreadLocal.set(true);
148: try {
149: if (context.getCurrentState() == null) {
150: context.setCurrentState(startState);
151: }
152: processEvents(eventQueue);
153: } finally {
154: processingThreadLocal.set(false);
155: }
156: }
157: }
158:
159: }
160:
161: private void processEvents(LinkedList<Event> eventQueue) {
162: while (!eventQueue.isEmpty()) {
163: Event event = eventQueue.removeFirst();
164: StateContext context = event.getContext();
165: handle(context.getCurrentState(), event);
166: }
167: }
168:
169: private void handle(State state, Event event) {
170: StateContext context = event.getContext();
171:
172: for (Transition t : state.getTransitions()) {
173: if (log.isDebugEnabled()) {
174: log.debug("Trying transition " + t);
175: }
176:
177: try {
178: if (t.execute(event)) {
179: if (log.isDebugEnabled()) {
180: log.debug("Transition " + t
181: + " executed successfully.");
182: }
183: setCurrentState(context, t.getNextState());
184:
185: return;
186: }
187: } catch (BreakAndContinueException bace) {
188: if (log.isDebugEnabled()) {
189: log.debug("BreakAndContinueException thrown in "
190: + "transition " + t
191: + ". Continuing with next transition.");
192: }
193: } catch (BreakAndGotoException bage) {
194: State newState = getState(bage.getStateId());
195:
196: if (bage.isNow()) {
197: if (log.isDebugEnabled()) {
198: log.debug("BreakAndGotoException thrown in "
199: + "transition " + t
200: + ". Moving to state "
201: + newState.getId() + " now.");
202: }
203: setCurrentState(context, newState);
204: handle(newState, event);
205: } else {
206: if (log.isDebugEnabled()) {
207: log.debug("BreakAndGotoException thrown in "
208: + "transition " + t
209: + ". Moving to state "
210: + newState.getId() + " next.");
211: }
212: setCurrentState(context, newState);
213: }
214: return;
215: } catch (BreakAndCallException bace) {
216: State newState = getState(bace.getStateId());
217:
218: Stack<State> callStack = getCallStack(context);
219: State returnTo = bace.getReturnToStateId() != null ? getState(bace
220: .getReturnToStateId())
221: : context.getCurrentState();
222: callStack.push(returnTo);
223:
224: if (bace.isNow()) {
225: if (log.isDebugEnabled()) {
226: log.debug("BreakAndCallException thrown in "
227: + "transition " + t
228: + ". Moving to state "
229: + newState.getId() + " now.");
230: }
231: setCurrentState(context, newState);
232: handle(newState, event);
233: } else {
234: if (log.isDebugEnabled()) {
235: log.debug("BreakAndCallException thrown in "
236: + "transition " + t
237: + ". Moving to state "
238: + newState.getId() + " next.");
239: }
240: setCurrentState(context, newState);
241: }
242: return;
243: } catch (BreakAndReturnException bare) {
244: Stack<State> callStack = getCallStack(context);
245: State newState = callStack.pop();
246:
247: if (bare.isNow()) {
248: if (log.isDebugEnabled()) {
249: log.debug("BreakAndReturnException thrown in "
250: + "transition " + t
251: + ". Moving to state "
252: + newState.getId() + " now.");
253: }
254: setCurrentState(context, newState);
255: handle(newState, event);
256: } else {
257: if (log.isDebugEnabled()) {
258: log.debug("BreakAndReturnException thrown in "
259: + "transition " + t
260: + ". Moving to state "
261: + newState.getId() + " next.");
262: }
263: setCurrentState(context, newState);
264: }
265: return;
266: }
267: }
268:
269: /*
270: * No transition could handle the event. Try with the parent state if
271: * there is one.
272: */
273:
274: if (state.getParent() != null) {
275: handle(state.getParent(), event);
276: } else {
277: throw new UnhandledEventException(event);
278: }
279: }
280:
281: private Stack<State> getCallStack(StateContext context) {
282: @SuppressWarnings("unchecked")
283: Stack<State> callStack = (Stack<State>) context
284: .getAttribute(CALL_STACK);
285: if (callStack == null) {
286: callStack = new Stack<State>();
287: context.setAttribute(CALL_STACK, callStack);
288: }
289: return callStack;
290: }
291:
292: private void setCurrentState(StateContext context, State newState) {
293: if (newState != null) {
294: if (log.isDebugEnabled()) {
295: if (newState != context.getCurrentState()) {
296: log.debug("Leaving state "
297: + context.getCurrentState().getId());
298: log.debug("Entering state " + newState.getId());
299: }
300: }
301: context.setCurrentState(newState);
302: }
303: }
304:
305: }
|