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.transition;
    21
    22  import java.lang.reflect.InvocationTargetException;
    23  import java.lang.reflect.Method;
    24  import java.util.Arrays;
    25
    26  import org.apache.commons.lang.builder.EqualsBuilder;
    27  import org.apache.commons.lang.builder.HashCodeBuilder;
    28  import org.apache.commons.lang.builder.ToStringBuilder;
    29  import org.apache.mina.statemachine.State;
    30  import org.apache.mina.statemachine.StateMachine;
    31  import org.apache.mina.statemachine.StateMachineFactory;
    32  import org.apache.mina.statemachine.annotation.Transition;
    33  import org.apache.mina.statemachine.context.StateContext;
    34  import org.apache.mina.statemachine.event.Event;
    35  import org.slf4j.Logger;
    36  import org.slf4j.LoggerFactory;
    37
    38  /**
    39   * {@link Transition} which invokes a {@link Method}. The {@link Method} will
    40   * only be invoked if its argument types actually matches a subset of the
    41   * {@link Event}'s argument types. The argument types are matched in order so
    42   * you must make sure the order of the method's arguments corresponds to the
    43   * order of the event's arguments.
    44   *<p>
    45   * If the first method argument type matches
    46   * {@link Event} the current {@link Event} will be bound to that argument. In
    47   * the same manner the second argument (or first if the method isn't interested
    48   * in the current {@link Event}) can have the {@link StateContext} type and will
    49   * in that case be bound to the current {@link StateContext}.
    50   * </p>
    51   * <p>
    52   * Normally you wouldn't create instances of this class directly but rather use the
    53   * {@link Transition} annotation to define the methods which should be used as
    54   * transitions in your state machine and then let {@link StateMachineFactory} create a
    55   * {@link StateMachine} for you.
    56   * </p>
    57   *
    58   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    59   */
    60  public class MethodTransition extends AbstractTransition {
    61      private static final Logger LOGGER = LoggerFactory.getLogger(MethodTransition.class);
    62
    63      private static final Object[] EMPTY_ARGUMENTS = new Object[0];
    64
    65      private final Method method;
    66
    67      private final Object target;
    68
    69      /**
    70       * Creates a new instance with the specified {@link State} as next state
    71       * and for the specified {@link Event} id.
    72       *
    73       * @param eventId the {@link Event} id.
    74       * @param nextState the next {@link State}.
    75       * @param method the target method.
    76       * @param target the target object.
    77       */
    78      public MethodTransition(Object eventId, State nextState, Method method, Object target) {
    79          super(eventId, nextState);
    80          this.method = method;
    81          this.target = target;
    82      }
    83
    84      /**
    85       * Creates a new instance which will loopback to the same {@link State}
    86       * for the specified {@link Event} id.
    87       *
    88       * @param eventId the {@link Event} id.
    89       * @param method the target method.
    90       * @param target the target object.
    91       */
    92      public MethodTransition(Object eventId, Method method, Object target) {
    93          this(eventId, null, method, target);
    94      }
    95
    96      /**
    97       * Creates a new instance with the specified {@link State} as next state
    98       * and for the specified {@link Event} id. The target {@link Method} will
    99       * be the method in the specified target object with the same name as the
    100      * specified {@link Event} id.
    101      *
    102      * @param eventId the {@link Event} id.
    103      * @param nextState the next {@link State}.
    104      * @param target the target object.
    105      * @throws NoSuchMethodException if no method could be found with a name
    106      *         equal to the {@link Event} id.
    107      * @throws AmbiguousMethodException if more than one method was found with
    108      *         a name equal to the {@link Event} id.
    109      */
    110     public MethodTransition(Object eventId, State nextState, Object target) {
    111         this(eventId, nextState, eventId.toString(), target);
    112     }
    113
    114     /**
    115      * Creates a new instance which will loopback to the same {@link State}
    116      * for the specified {@link Event} id. The target {@link Method} will
    117      * be the method in the specified target object with the same name as the
    118      * specified {@link Event} id.
    119      *
    120      * @param eventId the {@link Event} id.
    121      * @param target the target object.
    122      * @throws NoSuchMethodException if no method could be found with a name
    123      *         equal to the {@link Event} id.
    124      * @throws AmbiguousMethodException if more than one method was found with
    125      *         a name equal to the {@link Event} id.
    126      */
    127     public MethodTransition(Object eventId, Object target) {
    128         this(eventId, eventId.toString(), target);
    129     }
    130
    131     /**
    132      * Creates a new instance which will loopback to the same {@link State}
    133      * for the specified {@link Event} id.
    134      *
    135      * @param eventId the {@link Event} id.
    136      * @param methodName the name of the target {@link Method}.
    137      * @param target the target object.
    138      * @throws NoSuchMethodException if the method could not be found.
    139      * @throws AmbiguousMethodException if there are more than one method with
    140      *         the specified name.
    141      */
    142     public MethodTransition(Object eventId, String methodName, Object target) {
    143         this(eventId, null, methodName, target);
    144     }
    145
    146     /**
    147      * Creates a new instance with the specified {@link State} as next state
    148      * and for the specified {@link Event} id.
    149      *
    150      * @param eventId the {@link Event} id.
    151      * @param nextState the next {@link State}.
    152      * @param methodName the name of the target {@link Method}.
    153      * @param target the target object.
    154      * @throws NoSuchMethodException if the method could not be found.
    155      * @throws AmbiguousMethodException if there are more than one method with
    156      *         the specified name.
    157      */
    158     public MethodTransition(Object eventId, State nextState, String methodName, Object target) {
    159         super(eventId, nextState);
    160
    161         this.target = target;
    162
    163         Method[] candidates = target.getClass().getMethods();
    164         Method result = null;
    165         for (int i = 0; i < candidates.length; i++) {
    166             if (candidates[i].getName().equals(methodName)) {
    167                 if (result != null) {
    168                     throw new AmbiguousMethodException(methodName);
    169                 }
    170                 result = candidates[i];
    171             }
    172         }
    173
    174         if (result == null) {
    175             throw new NoSuchMethodException(methodName);
    176         }
    177
    178         this.method = result;
    179     }
    180
    181     /**
    182      * Returns the target {@link Method}.
    183      *
    184      * @return the method.
    185      */
    186     public Method getMethod() {
    187         return method;
    188     }
    189
    190     /**
    191      * Returns the target object.
    192      *
    193      * @return the target object.
    194      */
    195     public Object getTarget() {
    196         return target;
    197     }
    198
    199     public boolean doExecute(Event event) {
    200         Class<?>[] types = method.getParameterTypes();
    201
    202         if (types.length == 0) {
    203             invokeMethod(EMPTY_ARGUMENTS);
    204             return true;
    205         }
    206
    207         if (types.length > 2 + event.getArguments().length) {
    208             return false;
    209         }
    210
    211         Object[] args = new Object[types.length];
    212
    213         int i = 0;
    214         if (match(types[i], event, Event.class)) {
    215             args[i++] = event;
    216         }
    217         if (i < args.length && match(types[i], event.getContext(), StateContext.class)) {
    218             args[i++] = event.getContext();
    219         }
    220         Object[] eventArgs = event.getArguments();
    221         for (int j = 0; i < args.length && j < eventArgs.length; j++) {
    222             if (match(types[i], eventArgs[j], Object.class)) {
    223                 args[i++] = eventArgs[j];
    224             }
    225         }
    226
    227         if (args.length > i) {
    228             return false;
    229         }
    230
    231         invokeMethod(args);
    232
    233         return true;
    234     }
    235
    236     @SuppressWarnings("unchecked")
    237     private boolean match(Class<?> paramType, Object arg, Class argType) {
    238         if (paramType.isPrimitive()) {
    239             if (paramType.equals(Boolean.TYPE)) {
    240                 return arg instanceof Boolean;
    241             }
    242             if (paramType.equals(Integer.TYPE)) {
    243                 return arg instanceof Integer;
    244             }
    245             if (paramType.equals(Long.TYPE)) {
    246                 return arg instanceof Long;
    247             }
    248             if (paramType.equals(Short.TYPE)) {
    249                 return arg instanceof Short;
    250             }
    251             if (paramType.equals(Byte.TYPE)) {
    252                 return arg instanceof Byte;
    253             }
    254             if (paramType.equals(Double.TYPE)) {
    255                 return arg instanceof Double;
    256             }
    257             if (paramType.equals(Float.TYPE)) {
    258                 return arg instanceof Float;
    259             }
    260             if (paramType.equals(Character.TYPE)) {
    261                 return arg instanceof Character;
    262             }
    263         }
    264         return argType.isAssignableFrom(paramType) && paramType.isAssignableFrom(arg.getClass());
    265     }
    266
    267     private void invokeMethod(Object[] arguments) {
    268         try {
    269             if (LOGGER.isDebugEnabled()) {
    270                 LOGGER.debug("Executing method " + method + " with arguments " + Arrays.asList(arguments));
    271             }
    272             method.invoke(target, arguments);
    273         } catch (InvocationTargetException ite) {
    274             if (ite.getCause() instanceof RuntimeException) {
    275                 throw (RuntimeException) ite.getCause();
    276             }
    277             throw new MethodInvocationException(method, ite);
    278         } catch (IllegalAccessException iae) {
    279             throw new MethodInvocationException(method, iae);
    280         }
    281     }
    282
    283     public boolean equals(Object o) {
    284         if (!(o instanceof MethodTransition)) {
    285             return false;
    286         }
    287         if (o == this) {
    288             return true;
    289         }
    290         MethodTransition that = (MethodTransition) o;
    291         return new EqualsBuilder().appendSuper(super.equals(that)).append(method, that.method)
    292                 .append(target, that.target).isEquals();
    293     }
    294
    295     public int hashCode() {
    296         return new HashCodeBuilder(13, 33).appendSuper(super.hashCode()).append(method).append(target).toHashCode();
    297     }
    298
    299     public String toString() {
    300         return new ToStringBuilder(this).appendSuper(super.toString()).append("method", method)
    301                 .append("target", target).toString();
    302     }
    303 }