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