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 }