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      private static final Object[] EMPTY_ARGUMENTS = new Object[0];
63      
64      private final Method method;
65      private final Object target;
66  
67      /**
68       * Creates a new instance with the specified {@link State} as next state 
69       * and for the specified {@link Event} id.
70       * 
71       * @param eventId the {@link Event} id.
72       * @param nextState the next {@link State}.
73       * @param method the target method.
74       * @param target the target object.
75       */
76      public MethodTransition(Object eventId, State nextState, Method method, Object target) {
77          super(eventId, nextState);
78          this.method = method;
79          this.target = target;
80      }
81  
82      /**
83       * Creates a new instance which will loopback to the same {@link State} 
84       * for the specified {@link Event} id.
85       * 
86       * @param eventId the {@link Event} id.
87       * @param method the target method.
88       * @param target the target object.
89       */
90      public MethodTransition(Object eventId, Method method, Object target) {
91          this(eventId, null, method, target);
92      }
93      
94      /**
95       * Creates a new instance with the specified {@link State} as next state 
96       * and for the specified {@link Event} id. The target {@link Method} will
97       * be the method in the specified target object with the same name as the
98       * specified {@link Event} id.
99       * 
100      * @param eventId the {@link Event} id.
101      * @param nextState the next {@link State}.
102      * @param target the target object.
103      * @throws NoSuchMethodException if no method could be found with a name 
104      *         equal to the {@link Event} id.
105      * @throws AmbiguousMethodException if more than one method was found with 
106      *         a name equal to the {@link Event} id.
107      */
108     public MethodTransition(Object eventId, State nextState, Object target) {
109         this(eventId, nextState, eventId.toString(), target);
110     }
111     
112     /**
113      * Creates a new instance which will loopback to the same {@link State} 
114      * for the specified {@link Event} id. The target {@link Method} will
115      * be the method in the specified target object with the same name as the
116      * specified {@link Event} id.
117      * 
118      * @param eventId the {@link Event} id.
119      * @param target the target object.
120      * @throws NoSuchMethodException if no method could be found with a name 
121      *         equal to the {@link Event} id.
122      * @throws AmbiguousMethodException if more than one method was found with 
123      *         a name equal to the {@link Event} id.
124      */
125     public MethodTransition(Object eventId, Object target) {
126         this(eventId, eventId.toString(), target);
127     }
128 
129     /**
130      * Creates a new instance which will loopback to the same {@link State} 
131      * for the specified {@link Event} id.
132      * 
133      * @param eventId the {@link Event} id.
134      * @param methodName the name of the target {@link Method}.
135      * @param target the target object.
136      * @throws NoSuchMethodException if the method could not be found.
137      * @throws AmbiguousMethodException if there are more than one method with 
138      *         the specified name.
139      */
140     public MethodTransition(Object eventId, String methodName, Object target) {
141         this(eventId, null, methodName, target);
142     }
143     
144     /**
145      * Creates a new instance with the specified {@link State} as next state 
146      * and for the specified {@link Event} id.
147      * 
148      * @param eventId the {@link Event} id.
149      * @param nextState the next {@link State}.
150      * @param methodName the name of the target {@link Method}.
151      * @param target the target object.
152      * @throws NoSuchMethodException if the method could not be found.
153      * @throws AmbiguousMethodException if there are more than one method with 
154      *         the specified name.
155      */
156     public MethodTransition(Object eventId, State nextState, String methodName, Object target) {
157         super(eventId, nextState);
158 
159         this.target = target;
160         
161         Method[] candidates = target.getClass().getMethods();
162         Method result = null;
163         for (int i = 0; i < candidates.length; i++) {
164             if (candidates[i].getName().equals(methodName)) {
165                 if (result != null) {
166                     throw new AmbiguousMethodException(methodName);
167                 }
168                 result = candidates[i];
169             }
170         }
171         
172         if (result == null) {
173             throw new NoSuchMethodException(methodName);
174         }
175         
176         this.method = result;
177     }
178     
179     /**
180      * Returns the target {@link Method}.
181      * 
182      * @return the method.
183      */
184     public Method getMethod() {
185         return method;
186     }
187 
188     /**
189      * Returns the target object.
190      * 
191      * @return the target object.
192      */
193     public Object getTarget() {
194         return target;
195     }
196 
197     public boolean doExecute(Event event) {
198         Class<?>[] types = method.getParameterTypes();
199         
200         if (types.length == 0) {
201             invokeMethod(EMPTY_ARGUMENTS);
202             return true;
203         }
204         
205         if (types.length > 2 + event.getArguments().length) {
206             return false;
207         }
208         
209         Object[] args = new Object[types.length];
210         
211         int i = 0;
212         if (match(types[i], event, Event.class)) {
213             args[i++] = event;
214         }
215         if (i < args.length && match(types[i], event.getContext(), StateContext.class)) {
216             args[i++] = event.getContext();
217         }
218         Object[] eventArgs = event.getArguments();
219         for (int j = 0; i < args.length && j < eventArgs.length; j++) {
220             if (match(types[i], eventArgs[j], Object.class)) {
221                 args[i++] = eventArgs[j];
222             }
223         }
224         
225         if (args.length > i) {
226             return false;
227         }
228         
229         invokeMethod(args);
230         
231         return true;
232     }
233     
234     @SuppressWarnings("unchecked")
235     private boolean match(Class<?> paramType, Object arg, Class argType) {
236         if (paramType.isPrimitive()) {
237             if (paramType.equals(Boolean.TYPE)) {
238                 return arg instanceof Boolean;
239             }
240             if (paramType.equals(Integer.TYPE)) {
241                 return arg instanceof Integer;
242             }
243             if (paramType.equals(Long.TYPE)) {
244                 return arg instanceof Long;
245             }
246             if (paramType.equals(Short.TYPE)) {
247                 return arg instanceof Short;
248             }
249             if (paramType.equals(Byte.TYPE)) {
250                 return arg instanceof Byte;
251             }
252             if (paramType.equals(Double.TYPE)) {
253                 return arg instanceof Double;
254             }
255             if (paramType.equals(Float.TYPE)) {
256                 return arg instanceof Float;
257             }
258             if (paramType.equals(Character.TYPE)) {
259                 return arg instanceof Character;
260             }
261         }
262         return argType.isAssignableFrom(paramType) 
263                 && paramType.isAssignableFrom(arg.getClass());
264     }
265 
266     private void invokeMethod(Object[] arguments) {
267         try {
268             if (LOGGER.isDebugEnabled()) {
269                 LOGGER.debug("Executing method " + method 
270                         + " 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()
292             .appendSuper(super.equals(that))
293             .append(method, that.method)
294             .append(target, that.target)
295             .isEquals();
296     }
297 
298     public int hashCode() {
299         return new HashCodeBuilder(13, 33).appendSuper(super.hashCode()).append(method).append(target).toHashCode();
300     }
301 
302     public String toString() {
303         return new ToStringBuilder(this)
304             .appendSuper(super.toString())
305             .append("method", method)
306             .append("target", target)
307             .toString();
308     }
309 }