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