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