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