1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.statemachine;
19
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.ArrayDeque;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Queue;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.Future;
31 import java.util.concurrent.ScheduledExecutorService;
32 import java.util.concurrent.TimeUnit;
33
34 public class StateMachine {
35
36 private static final Logger LOG = LoggerFactory.getLogger(StateMachine.class);
37
38 private static final String HANDLER_METHOD_NAME = "handleEvent";
39
40 private static ConcurrentHashMap<Class<?>, ConcurrentHashMap<Class<?>, Method>> stateCaches;
41
42 static {
43 stateCaches = new ConcurrentHashMap<>();
44 }
45
46 public static abstract class State {
47
48 protected final Fsm fsm;
49 private final ConcurrentHashMap<Class<?>, Method> handlerCache;
50
51 public State(Fsm fsm) {
52 this.fsm = fsm;
53
54 ConcurrentHashMap<Class<?>, Method> handlerCache = stateCaches.get(getClass());
55 if (handlerCache == null) {
56 handlerCache = new ConcurrentHashMap<>();
57 ConcurrentHashMap<Class<?>, Method> old = stateCaches.putIfAbsent(getClass(), handlerCache);
58 if (old != null) {
59 handlerCache = old;
60 }
61 }
62 this.handlerCache = handlerCache;
63 }
64
65 private Method findHandlerInternal(Class<?> state, Class<?> e) throws NoSuchMethodException {
66 Method[] methods = state.getMethods();
67 List<Method> candidates = new ArrayList<>();
68 for (Method m : methods) {
69 if (m.getName().equals(HANDLER_METHOD_NAME)
70 && State.class.isAssignableFrom(m.getReturnType())
71 && m.getGenericParameterTypes().length == 1) {
72 candidates.add(m);
73 }
74 }
75
76 Method best = null;
77 for (Method m : candidates) {
78 if (m.getParameterTypes()[0].isAssignableFrom(e)) {
79 if (best == null) {
80 best = m;
81 } else if (best.getParameterTypes()[0]
82 .isAssignableFrom(m.getParameterTypes()[0])) {
83 best = m;
84 }
85 }
86 }
87 if (best != null) {
88 best.setAccessible(true);
89 return best;
90 }
91 throw new NoSuchMethodException("Handler doesn't exist");
92 }
93
94 private Method findHandler(Class<?> event) throws NoSuchMethodException {
95 Method m = handlerCache.get(event);
96 if (m == null) {
97 m = findHandlerInternal(getClass(), event);
98 Method m2 = handlerCache.putIfAbsent(event, m);
99 if (m2 != null) {
100 m = m2;
101 }
102 }
103 return m;
104 }
105
106 State dispatch(Event e) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
107 return (State) findHandler(e.getClass()).invoke(this, e);
108 }
109
110 }
111
112 public interface Event {
113 }
114
115 public interface Fsm {
116 Fsm newChildFsm();
117
118 void setInitState(State initState);
119
120 void sendEvent(Event e);
121
122 Future<?> sendEvent(Event e, long delay, TimeUnit unit);
123
124 void deferEvent(DeferrableEvent e);
125 }
126
127 public static class FsmImpl implements Fsm {
128 ScheduledExecutorService executor;
129 private State state;
130 private Queue<DeferrableEvent> deferred;
131
132 public FsmImpl(ScheduledExecutorService executor) {
133 this.executor = executor;
134 state = null;
135 deferred = new ArrayDeque<>();
136 }
137
138 private void errorDeferredEvents(Throwable t) {
139 Queue<DeferrableEvent> oldDeferred = deferred;
140 deferred = new ArrayDeque<>();
141
142 for (DeferrableEvent e : oldDeferred) {
143 e.error(new IllegalStateException(t));
144 }
145 }
146
147 @Override
148 public Fsm newChildFsm() {
149 return new FsmImpl(executor);
150 }
151
152 @Override
153 public void setInitState(State initState) {
154 assert (state == null);
155 state = initState;
156 }
157
158 public State getState() {
159 return state;
160 }
161
162 void setState(final State curState, final State newState) {
163 if (curState != state) {
164 LOG.error("FSM-{}: Tried to transition from {} to {}, but current state is {}",
165 getFsmId(), state, newState, curState);
166 throw new IllegalArgumentException();
167 }
168 state = newState;
169
170 if (LOG.isDebugEnabled()) {
171 LOG.debug("FSM-{}: State transition {} -> {}", getFsmId(), curState, newState);
172 }
173 }
174
175 boolean processEvent(Event e) {
176 if (LOG.isDebugEnabled()) {
177 LOG.debug("FSM-{}: Received event {}@{} in state {}@{}",
178 getFsmId(), e.getClass().getSimpleName(),
179 System.identityHashCode(e),
180 state.getClass().getSimpleName(),
181 System.identityHashCode(state));
182 }
183 try {
184 State newState = state.dispatch(e);
185
186 if (newState != state) {
187 setState(state, newState);
188 return true;
189 }
190 } catch (Throwable t) {
191 LOG.error("Caught throwable while handling event", t);
192 errorDeferredEvents(t);
193 }
194 return false;
195 }
196
197 class FSMRunnable implements Runnable {
198 final Event e;
199
200 FSMRunnable(Event e) {
201 this.e = e;
202 }
203
204 @Override
205 public void run() {
206 boolean stateChanged = processEvent(e);
207 while (stateChanged) {
208 stateChanged = false;
209 Queue<DeferrableEvent> prevDeferred = deferred;
210 deferred = new ArrayDeque<>();
211 for (DeferrableEvent d : prevDeferred) {
212 if (stateChanged) {
213 deferred.add(d);
214 } else if (processEvent(d)) {
215 stateChanged = true;
216 }
217 }
218 }
219 }
220 }
221
222 @Override
223 public void sendEvent(final Event e) {
224 executor.submit(new FSMRunnable(e));
225 }
226
227 @Override
228 public Future<?> sendEvent(final Event e, final long delay, final TimeUnit unit) {
229 return executor.schedule(new FSMRunnable(e), delay, unit);
230 }
231
232 @Override
233 public void deferEvent(DeferrableEvent e) {
234 if (LOG.isDebugEnabled()) {
235 LOG.debug("FSM-{}: deferred {}@{}",
236 getFsmId(), e.getClass().getSimpleName(), System.identityHashCode(e));
237 }
238 deferred.add(e);
239 }
240
241 int getFsmId() {
242 return System.identityHashCode(this);
243 }
244
245 @Override
246 protected void finalize() throws Throwable {
247 super.finalize();
248 LOG.debug("FSM-{}: Finalizing", getFsmId());
249 }
250 }
251
252 public interface DeferrableEvent extends Event {
253 void error(Throwable exception);
254 }
255
256 }