001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.statemachine.transition;
021:
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.util.Arrays;
025:
026: import org.apache.commons.lang.builder.EqualsBuilder;
027: import org.apache.commons.lang.builder.HashCodeBuilder;
028: import org.apache.commons.lang.builder.ToStringBuilder;
029: import org.apache.mina.statemachine.State;
030: import org.apache.mina.statemachine.StateMachine;
031: import org.apache.mina.statemachine.StateMachineFactory;
032: import org.apache.mina.statemachine.annotation.Transition;
033: import org.apache.mina.statemachine.context.StateContext;
034: import org.apache.mina.statemachine.event.Event;
035: import org.slf4j.Logger;
036: import org.slf4j.LoggerFactory;
037:
038: /**
039: * {@link Transition} which invokes a {@link Method}. The {@link Method} will
040: * only be invoked if its argument types actually matches a subset of the
041: * {@link Event}'s argument types. The argument types are matched in order so
042: * you must make sure the order of the method's arguments corresponds to the
043: * order of the event's arguments.
044: *<p>
045: * If the first method argument type matches
046: * {@link Event} the current {@link Event} will be bound to that argument. In
047: * the same manner the second argument (or first if the method isn't interested
048: * in the current {@link Event}) can have the {@link StateContext} type and will
049: * in that case be bound to the current {@link StateContext}.
050: * </p>
051: * <p>
052: * Normally you wouldn't create instances of this class directly but rather use the
053: * {@link Transition} annotation to define the methods which should be used as
054: * transitions in your state machine and then let {@link StateMachineFactory} create a
055: * {@link StateMachine} for you.
056: * </p>
057: *
058: * @author The Apache MINA Project (dev@mina.apache.org)
059: * @version $Rev: 592122 $, $Date: 2007-11-05 12:10:32 -0700 (Mon, 05 Nov 2007) $
060: */
061: public class MethodTransition extends AbstractTransition {
062: private static final Logger log = LoggerFactory
063: .getLogger(MethodTransition.class);
064: private static final Object[] EMPTY_ARGUMENTS = new Object[0];
065:
066: private final Method method;
067: private final Object target;
068:
069: /**
070: * Creates a new instance with the specified {@link State} as next state
071: * and for the specified {@link Event} id.
072: *
073: * @param eventId the {@link Event} id.
074: * @param nextState the next {@link State}.
075: * @param method the target method.
076: * @param target the target object.
077: */
078: public MethodTransition(Object eventId, State nextState,
079: Method method, Object target) {
080: super (eventId, nextState);
081: this .method = method;
082: this .target = target;
083: }
084:
085: /**
086: * Creates a new instance which will loopback to the same {@link State}
087: * for the specified {@link Event} id.
088: *
089: * @param eventId the {@link Event} id.
090: * @param method the target method.
091: * @param target the target object.
092: */
093: public MethodTransition(Object eventId, Method method, Object target) {
094: this (eventId, null, method, target);
095: }
096:
097: /**
098: * Creates a new instance with the specified {@link State} as next state
099: * and for the specified {@link Event} id. The target {@link Method} will
100: * be the method in the specified target object with the same name as the
101: * specified {@link Event} id.
102: *
103: * @param eventId the {@link Event} id.
104: * @param nextState the next {@link State}.
105: * @param target the target object.
106: * @throws NoSuchMethodException if no method could be found with a name
107: * equal to the {@link Event} id.
108: * @throws AmbiguousMethodException if more than one method was found with
109: * a name equal to the {@link Event} id.
110: */
111: public MethodTransition(Object eventId, State nextState,
112: Object target) {
113: this (eventId, nextState, eventId.toString(), target);
114: }
115:
116: /**
117: * Creates a new instance which will loopback to the same {@link State}
118: * for the specified {@link Event} id. The target {@link Method} will
119: * be the method in the specified target object with the same name as the
120: * specified {@link Event} id.
121: *
122: * @param eventId the {@link Event} id.
123: * @param target the target object.
124: * @throws NoSuchMethodException if no method could be found with a name
125: * equal to the {@link Event} id.
126: * @throws AmbiguousMethodException if more than one method was found with
127: * a name equal to the {@link Event} id.
128: */
129: public MethodTransition(Object eventId, Object target) {
130: this (eventId, eventId.toString(), target);
131: }
132:
133: /**
134: * Creates a new instance which will loopback to the same {@link State}
135: * for the specified {@link Event} id.
136: *
137: * @param eventId the {@link Event} id.
138: * @param methodName the name of the target {@link Method}.
139: * @param target the target object.
140: * @throws NoSuchMethodException if the method could not be found.
141: * @throws AmbiguousMethodException if there are more than one method with
142: * the specified name.
143: */
144: public MethodTransition(Object eventId, String methodName,
145: Object target) {
146: this (eventId, null, methodName, target);
147: }
148:
149: /**
150: * Creates a new instance with the specified {@link State} as next state
151: * and for the specified {@link Event} id.
152: *
153: * @param eventId the {@link Event} id.
154: * @param nextState the next {@link State}.
155: * @param methodName the name of the target {@link Method}.
156: * @param target the target object.
157: * @throws NoSuchMethodException if the method could not be found.
158: * @throws AmbiguousMethodException if there are more than one method with
159: * the specified name.
160: */
161: public MethodTransition(Object eventId, State nextState,
162: String methodName, Object target) {
163: super (eventId, nextState);
164:
165: this .target = target;
166:
167: Method[] candidates = target.getClass().getMethods();
168: Method result = null;
169: for (int i = 0; i < candidates.length; i++) {
170: if (candidates[i].getName().equals(methodName)) {
171: if (result != null) {
172: throw new AmbiguousMethodException(methodName);
173: }
174: result = candidates[i];
175: }
176: }
177:
178: if (result == null) {
179: throw new NoSuchMethodException(methodName);
180: }
181:
182: this .method = result;
183: }
184:
185: /**
186: * Returns the target {@link Method}.
187: *
188: * @return the method.
189: */
190: public Method getMethod() {
191: return method;
192: }
193:
194: /**
195: * Returns the target object.
196: *
197: * @return the target object.
198: */
199: public Object getTarget() {
200: return target;
201: }
202:
203: public boolean doExecute(Event event) {
204: Class<?>[] types = method.getParameterTypes();
205:
206: if (types.length == 0) {
207: invokeMethod(EMPTY_ARGUMENTS);
208: return true;
209: }
210:
211: if (types.length > 2 + event.getArguments().length) {
212: return false;
213: }
214:
215: Object[] args = new Object[types.length];
216:
217: int i = 0;
218: if (match(types[i], event, Event.class)) {
219: args[i++] = event;
220: }
221: if (i < args.length
222: && match(types[i], event.getContext(),
223: StateContext.class)) {
224: args[i++] = event.getContext();
225: }
226: Object[] eventArgs = event.getArguments();
227: for (int j = 0; i < args.length && j < eventArgs.length; j++) {
228: if (match(types[i], eventArgs[j], Object.class)) {
229: args[i++] = eventArgs[j];
230: }
231: }
232:
233: if (args.length > i) {
234: return false;
235: }
236:
237: invokeMethod(args);
238:
239: return true;
240: }
241:
242: @SuppressWarnings("unchecked")
243: private boolean match(Class<?> paramType, Object arg, Class argType) {
244: if (paramType.isPrimitive()) {
245: if (paramType.equals(Boolean.TYPE)) {
246: return arg instanceof Boolean;
247: }
248: if (paramType.equals(Integer.TYPE)) {
249: return arg instanceof Integer;
250: }
251: if (paramType.equals(Long.TYPE)) {
252: return arg instanceof Long;
253: }
254: if (paramType.equals(Short.TYPE)) {
255: return arg instanceof Short;
256: }
257: if (paramType.equals(Byte.TYPE)) {
258: return arg instanceof Byte;
259: }
260: if (paramType.equals(Double.TYPE)) {
261: return arg instanceof Double;
262: }
263: if (paramType.equals(Float.TYPE)) {
264: return arg instanceof Float;
265: }
266: if (paramType.equals(Character.TYPE)) {
267: return arg instanceof Character;
268: }
269: }
270: return argType.isAssignableFrom(paramType)
271: && paramType.isAssignableFrom(arg.getClass());
272: }
273:
274: private void invokeMethod(Object[] arguments) {
275: try {
276: if (log.isDebugEnabled()) {
277: log
278: .debug("Executing method " + method
279: + " with arguments "
280: + Arrays.asList(arguments));
281: }
282: method.invoke(target, arguments);
283: } catch (InvocationTargetException ite) {
284: if (ite.getCause() instanceof RuntimeException) {
285: throw (RuntimeException) ite.getCause();
286: }
287: throw new MethodInvocationException(method, ite);
288: } catch (IllegalAccessException iae) {
289: throw new MethodInvocationException(method, iae);
290: }
291: }
292:
293: public boolean equals(Object o) {
294: if (!(o instanceof MethodTransition)) {
295: return false;
296: }
297: if (o == this ) {
298: return true;
299: }
300: MethodTransition that = (MethodTransition) o;
301: return new EqualsBuilder().appendSuper(super .equals(that))
302: .append(method, that.method)
303: .append(target, that.target).isEquals();
304: }
305:
306: public int hashCode() {
307: return new HashCodeBuilder(13, 33)
308: .appendSuper(super .hashCode()).append(method).append(
309: target).toHashCode();
310: }
311:
312: public String toString() {
313: return new ToStringBuilder(this ).appendSuper(super .toString())
314: .append("method", method).append("target", target)
315: .toString();
316: }
317: }
|