View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
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   * Creates {@link StateMachine}s by reading {@link org.apache.mina.statemachine.annotation.State},
47   * {@link Transition} and {@link Transitions} (or equivalent) and {@link SelfTransition} annotations from one or more arbitrary
48   * objects.
49   * 
50   *
51   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
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       * Returns a new {@link StateMachineFactory} instance which creates
74       * {@link StateMachine}s by reading the specified {@link Transition}
75       * equivalent annotation.
76       * 
77       * @param transitionAnnotation the {@link Transition} equivalent annotation.
78       * @return the {@link StateMachineFactory}.
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       * Creates a new {@link StateMachine} from the specified handler object and
94       * using a start state with id <code>start</code>.
95       * 
96       * @param handler the object containing the annotations describing the
97       *        state machine.
98       * @return the {@link StateMachine} object.
99       */
100     public StateMachine create(Object handler) {
101         return create(handler, new Object[0]);
102     }
103 
104     /**
105      * Creates a new {@link StateMachine} from the specified handler object and
106      * using the {@link State} with the specified id as start state.
107      * 
108      * @param start the id of the start {@link State} to use.
109      * @param handler the object containing the annotations describing the
110      *        state machine.
111      * @return the {@link StateMachine} object.
112      */
113     public StateMachine create(String start, Object handler) {
114         return create(start, handler, new Object[0]);
115     }
116 
117     /**
118      * Creates a new {@link StateMachine} from the specified handler objects and
119      * using a start state with id <code>start</code>.
120      * 
121      * @param handler the first object containing the annotations describing the
122      *        state machine.
123      * @param handlers zero or more additional objects containing the
124      *        annotations describing the state machine.
125      * @return the {@link StateMachine} object.
126      */
127     public StateMachine create(Object handler, Object... handlers) {
128         return create("start", handler, handlers);
129     }
130 
131     /**
132      * Creates a new {@link StateMachine} from the specified handler objects and
133      * using the {@link State} with the specified id as start state.
134      * 
135      * @param start the id of the start {@link State} to use.
136      * @param handler the first object containing the annotations describing the
137      *        state machine.
138      * @param handlers zero or more additional objects containing the
139      *        annotations describing the state machine.
140      * @return the {@link StateMachine} object.
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             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 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                     // Move to the back of the list of fields for later
341                     // processing
342                     fields.add(f);
343                 }
344             }
345 
346             /*
347              * If no new states were added to states during this iteration it
348              * means that all fields in fields specify non-existent parents.
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 }