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          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       * Creates a new {@link StateMachine} from the specified handler object and
92       * using a start state with id <code>start</code>.
93       * 
94       * @param handler the object containing the annotations describing the
95       *        state machine.
96       * @return the {@link StateMachine} object.
97       */
98      public StateMachine create(Object handler) {
99          return create(handler, new Object[0]);
100     }
101 
102     /**
103      * Creates a new {@link StateMachine} from the specified handler object and
104      * using the {@link State} with the specified id as start state.
105      * 
106      * @param start the id of the start {@link State} to use.
107      * @param handler the object containing the annotations describing the
108      *        state machine.
109      * @return the {@link StateMachine} object.
110      */
111     public StateMachine create(String start, Object handler) {
112         return create(start, handler, new Object[0]);
113     }
114 
115     /**
116      * Creates a new {@link StateMachine} from the specified handler objects and
117      * using a start state with id <code>start</code>.
118      * 
119      * @param handler the first object containing the annotations describing the
120      *        state machine.
121      * @param handlers zero or more additional objects containing the
122      *        annotations describing the state machine.
123      * @return the {@link StateMachine} object.
124      */
125     public StateMachine create(Object handler, Object... handlers) {
126         return create("start", handler, handlers);
127     }
128 
129     /**
130      * Creates a new {@link StateMachine} from the specified handler objects and
131      * using the {@link State} with the specified id as start state.
132      * 
133      * @param start the id of the start {@link State} to use.
134      * @param handler the first object containing the annotations describing the
135      *        state machine.
136      * @param handlers zero or more additional objects containing the
137      *        annotations describing the state machine.
138      * @return the {@link StateMachine} object.
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                     // Move to the back of the list of fields for later
322                     // processing
323                     fields.add(f);
324                 }
325             }
326 
327             /*
328              * If no new states were added to states during this iteration it
329              * means that all fields in fields specify non-existent parents.
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 (Throwable t) {
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 (Throwable t) {
413                 throw new StateMachineCreationException("Could not get parameter '" + name
414                         + "' from Transitions annotation " + transitionsclazz);
415             }
416         }
417     }
418 }