View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
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   * Represents a complete state machine. Contains a collection of {@link State}
38   * objects connected by {@link Transition}s. Normally you wouldn't create 
39   * instances of this class directly but rather use the 
40   * {@link org.apache.mina.statemachine.annotation.State} annotation to define
41   * your states and then let {@link StateMachineFactory} create a 
42   * {@link StateMachine} for you.
43   *
44   * @author The Apache MINA Project (dev@mina.apache.org)
45   * @version $Rev: 641052 $, $Date: 2008-03-25 23:22:35 +0100 (mar, 25 mar 2008) $
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       * Creates a new instance using the specified {@link State}s and start
67       * state.
68       * 
69       * @param states the {@link State}s.
70       * @param startStateId the id of the start {@link State}.
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       * Creates a new instance using the specified {@link State}s and start
82       * state.
83       * 
84       * @param states the {@link State}s.
85       * @param startStateId the id of the start {@link State}.
86       */
87      public StateMachine(Collection<State> states, String startStateId) {
88          this(states.toArray(new State[0]), startStateId);
89      }
90      
91      /**
92       * Returns the {@link State} with the specified id.
93       * 
94       * @param id the id of the {@link State} to return.
95       * @return the {@link State}
96       * @throws NoSuchStateException if no matching {@link State} could be found.
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      * Returns an unmodifiable {@link Collection} of all {@link State}s used by
108      * this {@link StateMachine}.
109      * 
110      * @return the {@link State}s.
111      */
112     public Collection<State> getStates() {
113         return Collections.unmodifiableCollection(states.values());
114     }
115     
116     /**
117      * Processes the specified {@link Event} through this {@link StateMachine}.
118      * Normally you wouldn't call this directly but rather use
119      * {@link StateMachineProxyBuilder} to create a proxy for an interface of
120      * your choice. Any method calls on the proxy will be translated into
121      * {@link Event} objects and then fed to the {@link StateMachine} by the
122      * proxy using this method.
123      * 
124      * @param event the {@link Event} to be handled.
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                  * This thread is already processing an event. Queue this 
136                  * event.
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          * No transition could handle the event. Try with the parent state if
260          * there is one.
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 }