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.lang.annotation.Annotation;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.LinkedHashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.apache.mina.statemachine.annotation.OnEntry;
36 import org.apache.mina.statemachine.annotation.OnExit;
37 import org.apache.mina.statemachine.annotation.Transition;
38 import org.apache.mina.statemachine.annotation.TransitionAnnotation;
39 import org.apache.mina.statemachine.annotation.Transitions;
40 import org.apache.mina.statemachine.event.Event;
41 import org.apache.mina.statemachine.transition.MethodSelfTransition;
42 import org.apache.mina.statemachine.transition.MethodTransition;
43 import org.apache.mina.statemachine.transition.SelfTransition;
44
45
46
47
48
49
50
51
52
53 public class StateMachineFactory {
54 private final Class<? extends Annotation> transitionAnnotation;
55
56 private final Class<? extends Annotation> transitionsAnnotation;
57
58 private final Class<? extends Annotation> entrySelfTransitionsAnnotation;
59
60 private final Class<? extends Annotation> exitSelfTransitionsAnnotation;
61
62 protected StateMachineFactory(Class<? extends Annotation> transitionAnnotation,
63 Class<? extends Annotation> transitionsAnnotation,
64 Class<? extends Annotation> entrySelfTransitionsAnnotation,
65 Class<? extends Annotation> exitSelfTransitionsAnnotation) {
66 this.transitionAnnotation = transitionAnnotation;
67 this.transitionsAnnotation = transitionsAnnotation;
68 this.entrySelfTransitionsAnnotation = entrySelfTransitionsAnnotation;
69 this.exitSelfTransitionsAnnotation = exitSelfTransitionsAnnotation;
70 }
71
72
73
74
75
76
77
78
79
80 public static StateMachineFactory getInstance(Class<? extends Annotation> transitionAnnotation) {
81 TransitionAnnotation a = transitionAnnotation.getAnnotation(TransitionAnnotation.class);
82
83 if (a == null) {
84 throw new IllegalArgumentException("The annotation class " + transitionAnnotation
85 + " has not been annotated with the " + TransitionAnnotation.class.getName() + " annotation");
86 }
87
88 return new StateMachineFactory(transitionAnnotation, a.value(), OnEntry.class, OnExit.class);
89
90 }
91
92
93
94
95
96
97
98
99
100 public StateMachine create(Object handler) {
101 return create(handler, new Object[0]);
102 }
103
104
105
106
107
108
109
110
111
112
113 public StateMachine create(String start, Object handler) {
114 return create(start, handler, new Object[0]);
115 }
116
117
118
119
120
121
122
123
124
125
126
127 public StateMachine create(Object handler, Object... handlers) {
128 return create("start", handler, handlers);
129 }
130
131
132
133
134
135
136
137
138
139
140
141
142 public StateMachine create(String start, Object handler, Object... handlers) {
143
144 Map<String, State> states = new HashMap<>();
145 List<Object> handlersList = new ArrayList<>(1 + handlers.length);
146 handlersList.add(handler);
147 handlersList.addAll(Arrays.asList(handlers));
148
149 LinkedList<Field> fields = new LinkedList<>();
150 for (Object h : handlersList) {
151 fields.addAll(getFields(h instanceof Class ? (Class<?>) h : h.getClass()));
152 }
153 for (State state : createStates(fields)) {
154 states.put(state.getId(), state);
155 }
156
157 if (!states.containsKey(start)) {
158 throw new StateMachineCreationException("Start state '" + start + "' not found.");
159 }
160
161 setupTransitions(transitionAnnotation, transitionsAnnotation, entrySelfTransitionsAnnotation,
162 exitSelfTransitionsAnnotation, states, handlersList);
163
164 return new StateMachine(states.values(), start);
165 }
166
167 private static void setupTransitions(Class<? extends Annotation> transitionAnnotation,
168 Class<? extends Annotation> transitionsAnnotation,
169 Class<? extends Annotation> onEntrySelfTransitionAnnotation,
170 Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, List<Object> handlers) {
171 for (Object handler : handlers) {
172 setupTransitions(transitionAnnotation, transitionsAnnotation, onEntrySelfTransitionAnnotation,
173 onExitSelfTransitionAnnotation, states, handler);
174 }
175 }
176
177 private static void setupSelfTransitions(Method m, Class<? extends Annotation> onEntrySelfTransitionAnnotation,
178 Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) {
179 if (m.isAnnotationPresent(OnEntry.class)) {
180 OnEntryorg/apache/mina/statemachine/annotation/OnEntry.html#OnEntry">OnEntry onEntryAnnotation = (OnEntry) m.getAnnotation(onEntrySelfTransitionAnnotation);
181 State state = states.get(onEntryAnnotation.value());
182
183 if (state == null) {
184 throw new StateMachineCreationException("Error encountered "
185 + "when processing onEntry annotation in method " + m + ". state " + onEntryAnnotation.value()
186 + " not Found.");
187
188 }
189
190 state.addOnEntrySelfTransaction(new MethodSelfTransition(m, handler));
191 }
192
193 if (m.isAnnotationPresent(OnExit.class)) {
194 OnExit./org/apache/mina/statemachine/annotation/OnExit.html#OnExit">OnExit onExitAnnotation = (OnExit) m.getAnnotation(onExitSelfTransitionAnnotation);
195 State state = states.get(onExitAnnotation.value());
196
197 if (state == null) {
198 throw new StateMachineCreationException("Error encountered "
199 + "when processing onExit annotation in method " + m + ". state " + onExitAnnotation.value()
200 + " not Found.");
201
202 }
203
204 state.addOnExitSelfTransaction(new MethodSelfTransition(m, handler));
205 }
206
207 }
208
209 private static void setupTransitions(Class<? extends Annotation> transitionAnnotation,
210 Class<? extends Annotation> transitionsAnnotation,
211 Class<? extends Annotation> onEntrySelfTransitionAnnotation,
212 Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) {
213
214 Method[] methods = handler.getClass().getDeclaredMethods();
215 Arrays.sort(methods, new Comparator<Method>() {
216 @Override
217 public int compare(Method m1, Method m2) {
218 return m1.toString().compareTo(m2.toString());
219 }
220 });
221
222 for (Method m : methods) {
223 setupSelfTransitions(m, onEntrySelfTransitionAnnotation, onExitSelfTransitionAnnotation, states, handler);
224
225 List<TransitionWrapper> transitionAnnotations = new ArrayList<>();
226
227 if (m.isAnnotationPresent(transitionAnnotation)) {
228 transitionAnnotations.add(new TransitionWrapper(transitionAnnotation, m
229 .getAnnotation(transitionAnnotation)));
230 }
231
232 if (m.isAnnotationPresent(transitionsAnnotation)) {
233 transitionAnnotations.addAll(Arrays.asList(new TransitionsWrapper(transitionAnnotation,
234 transitionsAnnotation, m.getAnnotation(transitionsAnnotation)).value()));
235 }
236
237 if (transitionAnnotations.isEmpty()) {
238 continue;
239 }
240
241 for (TransitionWrapper annotation : transitionAnnotations) {
242 Object[] eventIds = annotation.on();
243
244 if (eventIds.length == 0) {
245 throw new StateMachineCreationException("Error encountered when processing method " + m
246 + ". No event ids specified.");
247 }
248
249 if (annotation.in().length == 0) {
250 throw new StateMachineCreationException("Error encountered when processing method " + m
251 + ". No states specified.");
252 }
253
254 State next = null;
255
256 if (!annotation.next().equals(Transition.SELF)) {
257 next = states.get(annotation.next());
258
259 if (next == null) {
260 throw new StateMachineCreationException("Error encountered when processing method " + m
261 + ". Unknown next state: " + annotation.next() + ".");
262 }
263 }
264
265 for (Object event : eventIds) {
266 if (event == null) {
267 event = Event.WILDCARD_EVENT_ID;
268 }
269
270 if (!(event instanceof String)) {
271 event = event.toString();
272 }
273
274 for (String in : annotation.in()) {
275 State state = states.get(in);
276
277 if (state == null) {
278 throw new StateMachineCreationException("Error encountered when processing method "
279 + m + ". Unknown state: " + in + ".");
280 }
281
282 state.addTransition(new MethodTransition(event, next, m, handler), annotation.weight());
283 }
284 }
285 }
286 }
287 }
288
289 static List<Field> getFields(Class<?> clazz) {
290 LinkedList<Field> fields = new LinkedList<>();
291
292 for (Field f : clazz.getDeclaredFields()) {
293 if (!f.isAnnotationPresent(org.apache.mina.statemachine.annotation.State.class)) {
294 continue;
295 }
296
297 if ((f.getModifiers() & Modifier.STATIC) == 0 || (f.getModifiers() & Modifier.FINAL) == 0
298 || !f.getType().equals(String.class)) {
299 throw new StateMachineCreationException("Error encountered when processing field " + f
300 + ". Only static final String fields can be used with the @State annotation.");
301 }
302
303 if (!f.isAccessible()) {
304 f.setAccessible(true);
305 }
306
307 fields.add(f);
308 }
309
310 return fields;
311 }
312
313 static State[] createStates(List<Field> fields) {
314 LinkedHashMap<String, State> states = new LinkedHashMap<>();
315
316 while (!fields.isEmpty()) {
317 int size = fields.size();
318 int numStates = states.size();
319
320 for (int i = 0; i < size; i++) {
321 Field f = fields.remove(0);
322
323 String value = null;
324
325 try {
326 value = (String) f.get(null);
327 } catch (IllegalAccessException iae) {
328 throw new StateMachineCreationException("Error encountered when processing field " + f + ".",
329 iae);
330 }
331
332 org.apache.mina.statemachine.annotation.State stateAnnotation = f
333 .getAnnotation(org.apache.mina.statemachine.annotation.State.class);
334
335 if (stateAnnotation.value().equals(org.apache.mina.statemachine.annotation.State.ROOT)) {
336 states.put(value, new State(value));
337 } else if (states.containsKey(stateAnnotation.value())) {
338 states.put(value, new State(value, states.get(stateAnnotation.value())));
339 } else {
340
341
342 fields.add(f);
343 }
344 }
345
346
347
348
349
350 if (states.size() == numStates) {
351 throw new StateMachineCreationException("Error encountered while creating "
352 + "FSM. The following fields specify non-existing parent states: " + fields);
353 }
354 }
355
356 return states.values().toArray(new State[0]);
357 }
358
359 private static class TransitionWrapper {
360 private final Class<? extends Annotation> transitionClazz;
361
362 private final Annotation annotation;
363
364 public TransitionWrapper(Class<? extends Annotation> transitionClazz, Annotation annotation) {
365 this.transitionClazz = transitionClazz;
366 this.annotation = annotation;
367 }
368
369 Object[] on() {
370 return getParameter("on", Object[].class);
371 }
372
373 String[] in() {
374 return getParameter("in", String[].class);
375 }
376
377 String next() {
378 return getParameter("next", String.class);
379 }
380
381 int weight() {
382 return getParameter("weight", Integer.TYPE);
383 }
384
385 @SuppressWarnings("unchecked")
386 private <T> T getParameter(String name, Class<T> returnType) {
387 try {
388 Method m = transitionClazz.getMethod(name);
389
390 if (!returnType.isAssignableFrom(m.getReturnType())) {
391 throw new NoSuchMethodException();
392 }
393 return (T) m.invoke(annotation);
394 } catch (Exception e) {
395 throw new StateMachineCreationException("Could not get parameter '" + name
396 + "' from Transition annotation " + transitionClazz);
397 }
398 }
399 }
400
401 private static class TransitionsWrapper {
402 private final Class<? extends Annotation> transitionsclazz;
403
404 private final Class<? extends Annotation> transitionClazz;
405
406 private final Annotation annotation;
407
408 public TransitionsWrapper(Class<? extends Annotation> transitionClazz,
409 Class<? extends Annotation> transitionsclazz, Annotation annotation) {
410 this.transitionClazz = transitionClazz;
411 this.transitionsclazz = transitionsclazz;
412 this.annotation = annotation;
413 }
414
415 TransitionWrapper[] value() {
416 Annotation[] annos = getParameter("value", Annotation[].class);
417 TransitionWrapper[] wrappers = new TransitionWrapper[annos.length];
418
419 for (int i = 0; i < annos.length; i++) {
420 wrappers[i] = new TransitionWrapper(transitionClazz, annos[i]);
421 }
422
423 return wrappers;
424 }
425
426 @SuppressWarnings("unchecked")
427 private <T> T getParameter(String name, Class<T> returnType) {
428 try {
429 Method m = transitionsclazz.getMethod(name);
430
431 if (!returnType.isAssignableFrom(m.getReturnType())) {
432 throw new NoSuchMethodException();
433 }
434
435 return (T) m.invoke(annotation);
436 } catch (Exception e) {
437 throw new StateMachineCreationException("Could not get parameter '" + name
438 + "' from Transitions annotation " + transitionsclazz);
439 }
440 }
441 }
442 }