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.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   * Creates {@link StateMachine}s by reading {@link org.apache.mina.statemachine.annotation.State},
44   * {@link Transition} and {@link Transitions} (or equivalent) annotations from one or more arbitrary 
45   * objects.
46   * 
47   *
48   * @author The Apache MINA Project (dev@mina.apache.org)
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       * Returns a new {@link StateMachineFactory} instance which creates 
62       * {@link StateMachine}s by reading the specified {@link Transition}
63       * equivalent annotation.
64       * 
65       * @param transitionAnnotation the {@link Transition} equivalent annotation.
66       * @return the {@link StateMachineFactory}.
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       * Creates a new {@link StateMachine} from the specified handler object and
80       * using a start state with id <code>start</code>.
81       * 
82       * @param handler the object containing the annotations describing the 
83       *        state machine.
84       * @return the {@link StateMachine} object.
85       */
86      public StateMachine create(Object handler) {
87          return create(handler, new Object[0]);
88      }
89  
90      /**
91       * Creates a new {@link StateMachine} from the specified handler object and
92       * using the {@link State} with the specified id as start state.
93       * 
94       * @param start the id of the start {@link State} to use.
95       * @param handler the object containing the annotations describing the 
96       *        state machine.
97       * @return the {@link StateMachine} object.
98       */
99      public StateMachine create(String start, Object handler) {
100         return create(start, handler, new Object[0]);
101     }
102 
103     /**
104      * Creates a new {@link StateMachine} from the specified handler objects and
105      * using a start state with id <code>start</code>.
106      * 
107      * @param handler the first object containing the annotations describing the 
108      *        state machine.
109      * @param handlers zero or more additional objects containing the 
110      *        annotations describing the state machine.
111      * @return the {@link StateMachine} object.
112      */
113     public StateMachine create(Object handler, Object... handlers) {
114         return create("start", handler, handlers);
115     }
116     
117     /**
118      * Creates a new {@link StateMachine} from the specified handler objects and
119      * using the {@link State} with the specified id as start state.
120      * 
121      * @param start the id of the start {@link State} to use.
122      * @param handler the first object containing the annotations describing the 
123      *        state machine.
124      * @param handlers zero or more additional objects containing the 
125      *        annotations describing the state machine.
126      * @return the {@link StateMachine} object.
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                     // Move to the back of the list of fields for later
281                     // processing
282                     fields.add(f);
283                 }
284             }
285 
286             /*
287              * If no new states were added to states during this iteration it 
288              * means that all fields in fields specify non-existent parents.
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 }