blob: 0ee85c9d0be54776ec2c9f935a47b975afc95247 [file] [log] [blame]
/*
* 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);
}
}
}
}