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 }