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