| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.mina.statemachine; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.mina.statemachine.annotation.OnEntry; |
| import org.apache.mina.statemachine.annotation.OnExit; |
| import org.apache.mina.statemachine.annotation.Transition; |
| import org.apache.mina.statemachine.annotation.TransitionAnnotation; |
| import org.apache.mina.statemachine.annotation.Transitions; |
| import org.apache.mina.statemachine.event.Event; |
| import org.apache.mina.statemachine.transition.MethodSelfTransition; |
| import org.apache.mina.statemachine.transition.MethodTransition; |
| import org.apache.mina.statemachine.transition.SelfTransition; |
| |
| /** |
| * Creates {@link StateMachine}s by reading {@link org.apache.mina.statemachine.annotation.State}, |
| * {@link Transition} and {@link Transitions} (or equivalent) and {@link SelfTransition} annotations from one or more arbitrary |
| * objects. |
| * |
| * |
| * @author <a href="http://mina.apache.org">Apache MINA Project</a> |
| */ |
| public class StateMachineFactory { |
| private final Class<? extends Annotation> transitionAnnotation; |
| |
| private final Class<? extends Annotation> transitionsAnnotation; |
| |
| private final Class<? extends Annotation> entrySelfTransitionsAnnotation; |
| |
| private final Class<? extends Annotation> exitSelfTransitionsAnnotation; |
| |
| protected StateMachineFactory(Class<? extends Annotation> transitionAnnotation, |
| Class<? extends Annotation> transitionsAnnotation, |
| Class<? extends Annotation> entrySelfTransitionsAnnotation, |
| Class<? extends Annotation> exitSelfTransitionsAnnotation) { |
| this.transitionAnnotation = transitionAnnotation; |
| this.transitionsAnnotation = transitionsAnnotation; |
| this.entrySelfTransitionsAnnotation = entrySelfTransitionsAnnotation; |
| this.exitSelfTransitionsAnnotation = exitSelfTransitionsAnnotation; |
| } |
| |
| /** |
| * Returns a new {@link StateMachineFactory} instance which creates |
| * {@link StateMachine}s by reading the specified {@link Transition} |
| * equivalent annotation. |
| * |
| * @param transitionAnnotation the {@link Transition} equivalent annotation. |
| * @return the {@link StateMachineFactory}. |
| */ |
| public static StateMachineFactory getInstance(Class<? extends Annotation> transitionAnnotation) { |
| TransitionAnnotation a = transitionAnnotation.getAnnotation(TransitionAnnotation.class); |
| |
| if (a == null) { |
| throw new IllegalArgumentException("The annotation class " + transitionAnnotation |
| + " has not been annotated with the " + TransitionAnnotation.class.getName() + " annotation"); |
| } |
| |
| return new StateMachineFactory(transitionAnnotation, a.value(), OnEntry.class, OnExit.class); |
| |
| } |
| |
| /** |
| * Creates a new {@link StateMachine} from the specified handler object and |
| * using a start state with id <code>start</code>. |
| * |
| * @param handler the object containing the annotations describing the |
| * state machine. |
| * @return the {@link StateMachine} object. |
| */ |
| public StateMachine create(Object handler) { |
| return create(handler, new Object[0]); |
| } |
| |
| /** |
| * Creates a new {@link StateMachine} from the specified handler object and |
| * using the {@link State} with the specified id as start state. |
| * |
| * @param start the id of the start {@link State} to use. |
| * @param handler the object containing the annotations describing the |
| * state machine. |
| * @return the {@link StateMachine} object. |
| */ |
| public StateMachine create(String start, Object handler) { |
| return create(start, handler, new Object[0]); |
| } |
| |
| /** |
| * Creates a new {@link StateMachine} from the specified handler objects and |
| * using a start state with id <code>start</code>. |
| * |
| * @param handler the first object containing the annotations describing the |
| * state machine. |
| * @param handlers zero or more additional objects containing the |
| * annotations describing the state machine. |
| * @return the {@link StateMachine} object. |
| */ |
| public StateMachine create(Object handler, Object... handlers) { |
| return create("start", handler, handlers); |
| } |
| |
| /** |
| * Creates a new {@link StateMachine} from the specified handler objects and |
| * using the {@link State} with the specified id as start state. |
| * |
| * @param start the id of the start {@link State} to use. |
| * @param handler the first object containing the annotations describing the |
| * state machine. |
| * @param handlers zero or more additional objects containing the |
| * annotations describing the state machine. |
| * @return the {@link StateMachine} object. |
| */ |
| public StateMachine create(String start, Object handler, Object... handlers) { |
| |
| Map<String, State> states = new HashMap<>(); |
| List<Object> handlersList = new ArrayList<>(1 + handlers.length); |
| handlersList.add(handler); |
| handlersList.addAll(Arrays.asList(handlers)); |
| |
| LinkedList<Field> fields = new LinkedList<>(); |
| for (Object h : handlersList) { |
| fields.addAll(getFields(h instanceof Class ? (Class<?>) h : h.getClass())); |
| } |
| for (State state : createStates(fields)) { |
| states.put(state.getId(), state); |
| } |
| |
| if (!states.containsKey(start)) { |
| throw new StateMachineCreationException("Start state '" + start + "' not found."); |
| } |
| |
| setupTransitions(transitionAnnotation, transitionsAnnotation, entrySelfTransitionsAnnotation, |
| exitSelfTransitionsAnnotation, states, handlersList); |
| |
| return new StateMachine(states.values(), start); |
| } |
| |
| private static void setupTransitions(Class<? extends Annotation> transitionAnnotation, |
| Class<? extends Annotation> transitionsAnnotation, |
| Class<? extends Annotation> onEntrySelfTransitionAnnotation, |
| Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, List<Object> handlers) { |
| for (Object handler : handlers) { |
| setupTransitions(transitionAnnotation, transitionsAnnotation, onEntrySelfTransitionAnnotation, |
| onExitSelfTransitionAnnotation, states, handler); |
| } |
| } |
| |
| private static void setupSelfTransitions(Method m, Class<? extends Annotation> onEntrySelfTransitionAnnotation, |
| Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) { |
| if (m.isAnnotationPresent(OnEntry.class)) { |
| OnEntry onEntryAnnotation = (OnEntry) m.getAnnotation(onEntrySelfTransitionAnnotation); |
| State state = states.get(onEntryAnnotation.value()); |
| |
| if (state == null) { |
| throw new StateMachineCreationException("Error encountered " |
| + "when processing onEntry annotation in method " + m + ". state " + onEntryAnnotation.value() |
| + " not Found."); |
| |
| } |
| |
| state.addOnEntrySelfTransaction(new MethodSelfTransition(m, handler)); |
| } |
| |
| if (m.isAnnotationPresent(OnExit.class)) { |
| OnExit onExitAnnotation = (OnExit) m.getAnnotation(onExitSelfTransitionAnnotation); |
| State state = states.get(onExitAnnotation.value()); |
| |
| if (state == null) { |
| throw new StateMachineCreationException("Error encountered " |
| + "when processing onExit annotation in method " + m + ". state " + onExitAnnotation.value() |
| + " not Found."); |
| |
| } |
| |
| state.addOnExitSelfTransaction(new MethodSelfTransition(m, handler)); |
| } |
| |
| } |
| |
| private static void setupTransitions(Class<? extends Annotation> transitionAnnotation, |
| Class<? extends Annotation> transitionsAnnotation, |
| Class<? extends Annotation> onEntrySelfTransitionAnnotation, |
| Class<? extends Annotation> onExitSelfTransitionAnnotation, Map<String, State> states, Object handler) { |
| |
| Method[] methods = handler.getClass().getDeclaredMethods(); |
| Arrays.sort(methods, new Comparator<Method>() { |
| @Override |
| public int compare(Method m1, Method m2) { |
| return m1.toString().compareTo(m2.toString()); |
| } |
| }); |
| |
| for (Method m : methods) { |
| setupSelfTransitions(m, onEntrySelfTransitionAnnotation, onExitSelfTransitionAnnotation, states, handler); |
| |
| List<TransitionWrapper> transitionAnnotations = new ArrayList<>(); |
| |
| if (m.isAnnotationPresent(transitionAnnotation)) { |
| transitionAnnotations.add(new TransitionWrapper(transitionAnnotation, m |
| .getAnnotation(transitionAnnotation))); |
| } |
| |
| if (m.isAnnotationPresent(transitionsAnnotation)) { |
| transitionAnnotations.addAll(Arrays.asList(new TransitionsWrapper(transitionAnnotation, |
| transitionsAnnotation, m.getAnnotation(transitionsAnnotation)).value())); |
| } |
| |
| if (transitionAnnotations.isEmpty()) { |
| continue; |
| } |
| |
| for (TransitionWrapper annotation : transitionAnnotations) { |
| Object[] eventIds = annotation.on(); |
| |
| if (eventIds.length == 0) { |
| throw new StateMachineCreationException("Error encountered when processing method " + m |
| + ". No event ids specified."); |
| } |
| |
| if (annotation.in().length == 0) { |
| throw new StateMachineCreationException("Error encountered when processing method " + m |
| + ". No states specified."); |
| } |
| |
| State next = null; |
| |
| if (!annotation.next().equals(Transition.SELF)) { |
| next = states.get(annotation.next()); |
| |
| if (next == null) { |
| throw new StateMachineCreationException("Error encountered when processing method " + m |
| + ". Unknown next state: " + annotation.next() + "."); |
| } |
| } |
| |
| for (Object event : eventIds) { |
| if (event == null) { |
| event = Event.WILDCARD_EVENT_ID; |
| } |
| |
| if (!(event instanceof String)) { |
| event = event.toString(); |
| } |
| |
| for (String in : annotation.in()) { |
| State state = states.get(in); |
| |
| if (state == null) { |
| throw new StateMachineCreationException("Error encountered when processing method " |
| + m + ". Unknown state: " + in + "."); |
| } |
| |
| state.addTransition(new MethodTransition(event, next, m, handler), annotation.weight()); |
| } |
| } |
| } |
| } |
| } |
| |
| static List<Field> getFields(Class<?> clazz) { |
| LinkedList<Field> fields = new LinkedList<>(); |
| |
| for (Field f : clazz.getDeclaredFields()) { |
| if (!f.isAnnotationPresent(org.apache.mina.statemachine.annotation.State.class)) { |
| continue; |
| } |
| |
| if ((f.getModifiers() & Modifier.STATIC) == 0 || (f.getModifiers() & Modifier.FINAL) == 0 |
| || !f.getType().equals(String.class)) { |
| throw new StateMachineCreationException("Error encountered when processing field " + f |
| + ". Only static final String fields can be used with the @State annotation."); |
| } |
| |
| if (!f.isAccessible()) { |
| f.setAccessible(true); |
| } |
| |
| fields.add(f); |
| } |
| |
| return fields; |
| } |
| |
| static State[] createStates(List<Field> fields) { |
| LinkedHashMap<String, State> states = new LinkedHashMap<>(); |
| |
| while (!fields.isEmpty()) { |
| int size = fields.size(); |
| int numStates = states.size(); |
| |
| for (int i = 0; i < size; i++) { |
| Field f = fields.remove(0); |
| |
| String value = null; |
| |
| try { |
| value = (String) f.get(null); |
| } catch (IllegalAccessException iae) { |
| throw new StateMachineCreationException("Error encountered when processing field " + f + ".", |
| iae); |
| } |
| |
| org.apache.mina.statemachine.annotation.State stateAnnotation = f |
| .getAnnotation(org.apache.mina.statemachine.annotation.State.class); |
| |
| if (stateAnnotation.value().equals(org.apache.mina.statemachine.annotation.State.ROOT)) { |
| states.put(value, new State(value)); |
| } else if (states.containsKey(stateAnnotation.value())) { |
| states.put(value, new State(value, states.get(stateAnnotation.value()))); |
| } else { |
| // Move to the back of the list of fields for later |
| // processing |
| fields.add(f); |
| } |
| } |
| |
| /* |
| * If no new states were added to states during this iteration it |
| * means that all fields in fields specify non-existent parents. |
| */ |
| if (states.size() == numStates) { |
| throw new StateMachineCreationException("Error encountered while creating " |
| + "FSM. The following fields specify non-existing parent states: " + fields); |
| } |
| } |
| |
| return states.values().toArray(new State[0]); |
| } |
| |
| private static class TransitionWrapper { |
| private final Class<? extends Annotation> transitionClazz; |
| |
| private final Annotation annotation; |
| |
| public TransitionWrapper(Class<? extends Annotation> transitionClazz, Annotation annotation) { |
| this.transitionClazz = transitionClazz; |
| this.annotation = annotation; |
| } |
| |
| Object[] on() { |
| return getParameter("on", Object[].class); |
| } |
| |
| String[] in() { |
| return getParameter("in", String[].class); |
| } |
| |
| String next() { |
| return getParameter("next", String.class); |
| } |
| |
| int weight() { |
| return getParameter("weight", Integer.TYPE); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <T> T getParameter(String name, Class<T> returnType) { |
| try { |
| Method m = transitionClazz.getMethod(name); |
| |
| if (!returnType.isAssignableFrom(m.getReturnType())) { |
| throw new NoSuchMethodException(); |
| } |
| return (T) m.invoke(annotation); |
| } catch (Exception e) { |
| throw new StateMachineCreationException("Could not get parameter '" + name |
| + "' from Transition annotation " + transitionClazz); |
| } |
| } |
| } |
| |
| private static class TransitionsWrapper { |
| private final Class<? extends Annotation> transitionsclazz; |
| |
| private final Class<? extends Annotation> transitionClazz; |
| |
| private final Annotation annotation; |
| |
| public TransitionsWrapper(Class<? extends Annotation> transitionClazz, |
| Class<? extends Annotation> transitionsclazz, Annotation annotation) { |
| this.transitionClazz = transitionClazz; |
| this.transitionsclazz = transitionsclazz; |
| this.annotation = annotation; |
| } |
| |
| TransitionWrapper[] value() { |
| Annotation[] annos = getParameter("value", Annotation[].class); |
| TransitionWrapper[] wrappers = new TransitionWrapper[annos.length]; |
| |
| for (int i = 0; i < annos.length; i++) { |
| wrappers[i] = new TransitionWrapper(transitionClazz, annos[i]); |
| } |
| |
| return wrappers; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <T> T getParameter(String name, Class<T> returnType) { |
| try { |
| Method m = transitionsclazz.getMethod(name); |
| |
| if (!returnType.isAssignableFrom(m.getReturnType())) { |
| throw new NoSuchMethodException(); |
| } |
| |
| return (T) m.invoke(annotation); |
| } catch (Exception e) { |
| throw new StateMachineCreationException("Could not get parameter '" + name |
| + "' from Transitions annotation " + transitionsclazz); |
| } |
| } |
| } |
| } |