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