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