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; 021 022import java.lang.annotation.Annotation; 023import java.lang.reflect.Field; 024import java.lang.reflect.Method; 025import java.lang.reflect.Modifier; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Comparator; 029import java.util.HashMap; 030import java.util.LinkedHashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034 035import org.apache.mina.statemachine.annotation.OnEntry; 036import org.apache.mina.statemachine.annotation.OnExit; 037import org.apache.mina.statemachine.annotation.Transition; 038import org.apache.mina.statemachine.annotation.TransitionAnnotation; 039import org.apache.mina.statemachine.annotation.Transitions; 040import org.apache.mina.statemachine.event.Event; 041import org.apache.mina.statemachine.transition.MethodSelfTransition; 042import org.apache.mina.statemachine.transition.MethodTransition; 043import org.apache.mina.statemachine.transition.SelfTransition; 044 045/** 046 * Creates {@link StateMachine}s by reading {@link org.apache.mina.statemachine.annotation.State}, 047 * {@link Transition} and {@link Transitions} (or equivalent) and {@link SelfTransition} annotations from one or more arbitrary 048 * objects. 049 * 050 * 051 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 052 */ 053public class StateMachineFactory { 054 private final Class<? extends Annotation> transitionAnnotation; 055 056 private final Class<? extends Annotation> transitionsAnnotation; 057 058 private final Class<? extends Annotation> entrySelfTransitionsAnnotation; 059 060 private final Class<? extends Annotation> exitSelfTransitionsAnnotation; 061 062 protected StateMachineFactory(Class<? extends Annotation> transitionAnnotation, 063 Class<? extends Annotation> transitionsAnnotation, 064 Class<? extends Annotation> entrySelfTransitionsAnnotation, 065 Class<? extends Annotation> exitSelfTransitionsAnnotation) { 066 this.transitionAnnotation = transitionAnnotation; 067 this.transitionsAnnotation = transitionsAnnotation; 068 this.entrySelfTransitionsAnnotation = entrySelfTransitionsAnnotation; 069 this.exitSelfTransitionsAnnotation = exitSelfTransitionsAnnotation; 070 } 071 072 /** 073 * Returns a new {@link StateMachineFactory} instance which creates 074 * {@link StateMachine}s by reading the specified {@link Transition} 075 * equivalent annotation. 076 * 077 * @param transitionAnnotation the {@link Transition} equivalent annotation. 078 * @return the {@link StateMachineFactory}. 079 */ 080 public static StateMachineFactory getInstance(Class<? extends Annotation> transitionAnnotation) { 081 TransitionAnnotation a = transitionAnnotation.getAnnotation(TransitionAnnotation.class); 082 if (a == null) { 083 throw new IllegalArgumentException("The annotation class " + transitionAnnotation 084 + " has not been annotated with the " + TransitionAnnotation.class.getName() + " annotation"); 085 } 086 return new StateMachineFactory(transitionAnnotation, a.value(), OnEntry.class, OnExit.class); 087 088 } 089 090 /** 091 * Creates a new {@link StateMachine} from the specified handler object and 092 * using a start state with id <code>start</code>. 093 * 094 * @param handler the object containing the annotations describing the 095 * state machine. 096 * @return the {@link StateMachine} object. 097 */ 098 public StateMachine create(Object handler) { 099 return create(handler, new Object[0]); 100 } 101 102 /** 103 * Creates a new {@link StateMachine} from the specified handler object and 104 * using the {@link State} with the specified id as start state. 105 * 106 * @param start the id of the start {@link State} to use. 107 * @param handler the object containing the annotations describing the 108 * state machine. 109 * @return the {@link StateMachine} object. 110 */ 111 public StateMachine create(String start, Object handler) { 112 return create(start, handler, new Object[0]); 113 } 114 115 /** 116 * Creates a new {@link StateMachine} from the specified handler objects and 117 * using a start state with id <code>start</code>. 118 * 119 * @param handler the first object containing the annotations describing the 120 * state machine. 121 * @param handlers zero or more additional objects containing the 122 * annotations describing the state machine. 123 * @return the {@link StateMachine} object. 124 */ 125 public StateMachine create(Object handler, Object... handlers) { 126 return create("start", handler, handlers); 127 } 128 129 /** 130 * Creates a new {@link StateMachine} from the specified handler objects and 131 * using the {@link State} with the specified id as start state. 132 * 133 * @param start the id of the start {@link State} to use. 134 * @param handler the first object containing the annotations describing the 135 * state machine. 136 * @param handlers zero or more additional objects containing the 137 * annotations describing the state machine. 138 * @return the {@link StateMachine} object. 139 */ 140 public StateMachine create(String start, Object handler, Object... handlers) { 141 142 Map<String, State> states = new HashMap<String, State>(); 143 List<Object> handlersList = new ArrayList<Object>(1 + handlers.length); 144 handlersList.add(handler); 145 handlersList.addAll(Arrays.asList(handlers)); 146 147 LinkedList<Field> fields = new LinkedList<Field>(); 148 for (Object h : handlersList) { 149 fields.addAll(getFields(h instanceof Class ? (Class<?>) h : h.getClass())); 150 } 151 for (State state : createStates(fields)) { 152 states.put(state.getId(), state); 153 } 154 155 if (!states.containsKey(start)) { 156 throw new StateMachineCreationException("Start state '" + start + "' not found."); 157 } 158 159 setupTransitions(transitionAnnotation, transitionsAnnotation, entrySelfTransitionsAnnotation, 160 exitSelfTransitionsAnnotation, states, handlersList); 161 162 return new StateMachine(states.values(), start); 163 } 164 165 private static void setupTransitions(Class<? extends Annotation> transitionAnnotation, 166 Class<? extends Annotation> transitionsAnnotation, 167 Class<? extends Annotation> onEntrySelfTransitionAnnotation, 168 Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, List<Object> handlers) { 169 for (Object handler : handlers) { 170 setupTransitions(transitionAnnotation, transitionsAnnotation, onEntrySelfTransitionAnnotation, 171 onExitSelfTransitionAnnotation, states, handler); 172 } 173 } 174 175 private static void setupSelfTransitions(Method m, Class<? extends Annotation> onEntrySelfTransitionAnnotation, 176 Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) { 177 if (m.isAnnotationPresent(OnEntry.class)) { 178 OnEntry onEntryAnnotation = (OnEntry) m.getAnnotation(onEntrySelfTransitionAnnotation); 179 State state = states.get(onEntryAnnotation.value()); 180 if (state == null) { 181 throw new StateMachineCreationException("Error encountered " 182 + "when processing onEntry annotation in method " + m + ". state " + onEntryAnnotation.value() 183 + " not Found."); 184 185 } 186 state.addOnEntrySelfTransaction(new MethodSelfTransition(m, handler)); 187 } 188 189 if (m.isAnnotationPresent(OnExit.class)) { 190 OnExit onExitAnnotation = (OnExit) m.getAnnotation(onExitSelfTransitionAnnotation); 191 State state = states.get(onExitAnnotation.value()); 192 if (state == null) { 193 throw new StateMachineCreationException("Error encountered " 194 + "when processing onExit annotation in method " + m + ". state " + onExitAnnotation.value() 195 + " not Found."); 196 197 } 198 state.addOnExitSelfTransaction(new MethodSelfTransition(m, handler)); 199 } 200 201 } 202 203 private static void setupTransitions(Class<? extends Annotation> transitionAnnotation, 204 Class<? extends Annotation> transitionsAnnotation, 205 Class<? extends Annotation> onEntrySelfTransitionAnnotation, 206 Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) { 207 208 Method[] methods = handler.getClass().getDeclaredMethods(); 209 Arrays.sort(methods, new Comparator<Method>() { 210 public int compare(Method m1, Method m2) { 211 return m1.toString().compareTo(m2.toString()); 212 } 213 }); 214 215 for (Method m : methods) { 216 setupSelfTransitions(m, onEntrySelfTransitionAnnotation, onExitSelfTransitionAnnotation, states, handler); 217 218 List<TransitionWrapper> transitionAnnotations = new ArrayList<TransitionWrapper>(); 219 if (m.isAnnotationPresent(transitionAnnotation)) { 220 transitionAnnotations.add(new TransitionWrapper(transitionAnnotation, m 221 .getAnnotation(transitionAnnotation))); 222 } 223 if (m.isAnnotationPresent(transitionsAnnotation)) { 224 transitionAnnotations.addAll(Arrays.asList(new TransitionsWrapper(transitionAnnotation, 225 transitionsAnnotation, m.getAnnotation(transitionsAnnotation)).value())); 226 } 227 228 if (transitionAnnotations.isEmpty()) { 229 continue; 230 } 231 232 for (TransitionWrapper annotation : transitionAnnotations) { 233 Object[] eventIds = annotation.on(); 234 if (eventIds.length == 0) { 235 throw new StateMachineCreationException("Error encountered " + "when processing method " + m 236 + ". No event ids specified."); 237 } 238 if (annotation.in().length == 0) { 239 throw new StateMachineCreationException("Error encountered " + "when processing method " + m 240 + ". No states specified."); 241 } 242 243 State next = null; 244 if (!annotation.next().equals(Transition.SELF)) { 245 next = states.get(annotation.next()); 246 if (next == null) { 247 throw new StateMachineCreationException("Error encountered " + "when processing method " + m 248 + ". Unknown next state: " + annotation.next() + "."); 249 } 250 } 251 252 for (Object event : eventIds) { 253 if (event == null) { 254 event = Event.WILDCARD_EVENT_ID; 255 } 256 if (!(event instanceof String)) { 257 event = event.toString(); 258 } 259 for (String in : annotation.in()) { 260 State state = states.get(in); 261 if (state == null) { 262 throw new StateMachineCreationException("Error encountered " + "when processing method " 263 + m + ". Unknown state: " + in + "."); 264 } 265 266 state.addTransition(new MethodTransition(event, next, m, handler), annotation.weight()); 267 } 268 } 269 } 270 } 271 } 272 273 static List<Field> getFields(Class<?> clazz) { 274 LinkedList<Field> fields = new LinkedList<Field>(); 275 276 for (Field f : clazz.getDeclaredFields()) { 277 if (!f.isAnnotationPresent(org.apache.mina.statemachine.annotation.State.class)) { 278 continue; 279 } 280 281 if ((f.getModifiers() & Modifier.STATIC) == 0 || (f.getModifiers() & Modifier.FINAL) == 0 282 || !f.getType().equals(String.class)) { 283 throw new StateMachineCreationException("Error encountered when " + "processing field " + f 284 + ". Only static final " + "String fields can be used with the @State " + "annotation."); 285 } 286 287 if (!f.isAccessible()) { 288 f.setAccessible(true); 289 } 290 291 fields.add(f); 292 } 293 294 return fields; 295 } 296 297 static State[] createStates(List<Field> fields) { 298 LinkedHashMap<String, State> states = new LinkedHashMap<String, State>(); 299 300 while (!fields.isEmpty()) { 301 int size = fields.size(); 302 int numStates = states.size(); 303 for (int i = 0; i < size; i++) { 304 Field f = fields.remove(0); 305 306 String value = null; 307 try { 308 value = (String) f.get(null); 309 } catch (IllegalAccessException iae) { 310 throw new StateMachineCreationException("Error encountered when " + "processing field " + f + ".", 311 iae); 312 } 313 314 org.apache.mina.statemachine.annotation.State stateAnnotation = f 315 .getAnnotation(org.apache.mina.statemachine.annotation.State.class); 316 if (stateAnnotation.value().equals(org.apache.mina.statemachine.annotation.State.ROOT)) { 317 states.put(value, new State(value)); 318 } else if (states.containsKey(stateAnnotation.value())) { 319 states.put(value, new State(value, states.get(stateAnnotation.value()))); 320 } else { 321 // Move to the back of the list of fields for later 322 // processing 323 fields.add(f); 324 } 325 } 326 327 /* 328 * If no new states were added to states during this iteration it 329 * means that all fields in fields specify non-existent parents. 330 */ 331 if (states.size() == numStates) { 332 throw new StateMachineCreationException("Error encountered while creating " 333 + "FSM. The following fields specify non-existing " + "parent states: " + fields); 334 } 335 } 336 337 return states.values().toArray(new State[0]); 338 } 339 340 private static class TransitionWrapper { 341 private final Class<? extends Annotation> transitionClazz; 342 343 private final Annotation annotation; 344 345 public TransitionWrapper(Class<? extends Annotation> transitionClazz, Annotation annotation) { 346 this.transitionClazz = transitionClazz; 347 this.annotation = annotation; 348 } 349 350 Object[] on() { 351 return getParameter("on", Object[].class); 352 } 353 354 String[] in() { 355 return getParameter("in", String[].class); 356 } 357 358 String next() { 359 return getParameter("next", String.class); 360 } 361 362 int weight() { 363 return getParameter("weight", Integer.TYPE); 364 } 365 366 @SuppressWarnings("unchecked") 367 private <T> T getParameter(String name, Class<T> returnType) { 368 try { 369 Method m = transitionClazz.getMethod(name); 370 if (!returnType.isAssignableFrom(m.getReturnType())) { 371 throw new NoSuchMethodException(); 372 } 373 return (T) m.invoke(annotation); 374 } catch (Exception e) { 375 throw new StateMachineCreationException("Could not get parameter '" + name 376 + "' from Transition annotation " + transitionClazz); 377 } 378 } 379 } 380 381 private static class TransitionsWrapper { 382 private final Class<? extends Annotation> transitionsclazz; 383 384 private final Class<? extends Annotation> transitionClazz; 385 386 private final Annotation annotation; 387 388 public TransitionsWrapper(Class<? extends Annotation> transitionClazz, 389 Class<? extends Annotation> transitionsclazz, Annotation annotation) { 390 this.transitionClazz = transitionClazz; 391 this.transitionsclazz = transitionsclazz; 392 this.annotation = annotation; 393 } 394 395 TransitionWrapper[] value() { 396 Annotation[] annos = getParameter("value", Annotation[].class); 397 TransitionWrapper[] wrappers = new TransitionWrapper[annos.length]; 398 for (int i = 0; i < annos.length; i++) { 399 wrappers[i] = new TransitionWrapper(transitionClazz, annos[i]); 400 } 401 return wrappers; 402 } 403 404 @SuppressWarnings("unchecked") 405 private <T> T getParameter(String name, Class<T> returnType) { 406 try { 407 Method m = transitionsclazz.getMethod(name); 408 if (!returnType.isAssignableFrom(m.getReturnType())) { 409 throw new NoSuchMethodException(); 410 } 411 return (T) m.invoke(annotation); 412 } catch (Exception e) { 413 throw new StateMachineCreationException("Could not get parameter '" + name 414 + "' from Transitions annotation " + transitionsclazz); 415 } 416 } 417 } 418}