/* * 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.river.container.hsm; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Compiler for state machine instances. The "input" to the compiler is actually * a class that is annotated with the * * @RootState annotation. * */ class StateMachineCompiler { private static final Logger log = Logger.getLogger(StateMachineCompiler.class.getName(), MessageNames.BUNDLE_NAME); Class[] eventInterfaces = null; /** * Retrieve the event interface, as set by the * * @RootState annotation. * @return The event interface. */ Class[] getEventInterfaces() { return eventInterfaces; } MetaState compile(Class rootStateClass) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { log.log(Level.FINE, MessageNames.BEGINNING_COMPILE, new Object[]{rootStateClass.getName()}); findEventInterfaces(rootStateClass); // First pass: create all metastates MetaState rootMetaState = createMetaState(rootStateClass); // Second pass: Fill in event methods rootMetaState.visitAll(new MetaStateVisitor() { @Override public void visit(MetaState ms) { try { fillInEventMethods(ms); fillInGuardMethods(ms); fillInEntryMethods(ms); fillInExitMethods(ms); } catch (Exception ex) { throw new RuntimeException(ex); } } }); log.log(Level.FINE, MessageNames.COMPILE_COMPLETED, new Object[]{rootStateClass.getName()}); return rootMetaState; } private void findEventInterfaces(Class rootStateClass) { RootState rootStateAnnotation = (RootState) rootStateClass.getAnnotation(RootState.class); if (rootStateAnnotation == null || rootStateAnnotation.value() == null) { throw new RuntimeException("Root state class must specify @RootState(interfaceClass)."); } eventInterfaces = rootStateAnnotation.value(); } MetaState createMetaState(Class stateClass) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return createMetaState(null, stateClass); } MetaState createMetaState(MetaState parentMetaState, Class stateClass) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { MetaState metaState = new MetaState(); metaState.stateClass = stateClass; metaState.parent = parentMetaState; processSubstates(metaState); return metaState; } /** * Look for fields that are annotated with * * @State and fill in the required substate info. * @param metaState */ private void processSubstates(MetaState metaState) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { for (Field f : metaState.stateClass.getDeclaredFields()) { // Look for fields annotated with @State. State ann = (State) f.getAnnotation(State.class); if (ann == null) { continue; } SubstateInfo info = new SubstateInfo(); info.setField(f); metaState.substates.add(info); if (ann.value() == null) { throw new RuntimeException("@State needs a list of possible states"); } info.setPossibleMetaStates(createPossibleMetaStates(metaState, ann.value()).toArray(new MetaState[0])); Initial initialAnn = f.getAnnotation(Initial.class); if (initialAnn == null) { throw new RuntimeException("Need initial state for " + f.getName()); } MetaState initialMetaState = findMetaState(info.getPossibleMetaStates(), initialAnn.value()); if (initialMetaState == null) { throw new RuntimeException("Couldn't find a metastate that corresponds to " + initialAnn.value() + " in " + Utils.format(info.getPossibleMetaStates())); } info.setInitialMetaState(initialMetaState); /* While we're at it, set the active metastate. */ info.setActiveMetaState(info.getInitialMetaState()); Retained retainedAnn = f.getAnnotation(Retained.class); info.setRetained(retainedAnn != null); /* Add the non-retained metastate to the list of transitions-on-entry of the * parent metastat. */ if (!info.isRetained()) { metaState.entryTransitions.add(new TransitionOnSubstate(info, info.getInitialMetaState())); } } } private List<MetaState> createPossibleMetaStates(MetaState metaState, Class[] substateClasses) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { List<MetaState> metaStates = new ArrayList<MetaState>(); /* For each class, we'll need an instance. */ for (Class substateClass : substateClasses) { metaStates.add(createMetaState(metaState, substateClass)); } return metaStates; } private MetaState findMetaState(MetaState[] possibleMetaStates, Class value) { for (MetaState metaState : possibleMetaStates) { if (metaState.stateClass == value) { return metaState; } } return null; } private void fillInEventMethods(MetaState metaState) throws NoSuchMethodException { for (Class eventInterface : getEventInterfaces()) { for (Method m : eventInterface.getMethods()) { /* See if the state class has a method with the same name and * parameters. */ Method eventMethod = null; try { eventMethod = metaState.stateClass.getMethod(m.getName(), m.getParameterTypes()); } catch (NoSuchMethodException nsme) { // Silent catch - lets the event method remain null, which we check for. eventMethod=null; //Redundant but keeps PMD happy. } Operation operation = null; if (eventMethod != null) { if (eventMethod.getReturnType() != null && !m.getReturnType().isAssignableFrom(eventMethod.getReturnType())) { throw new RuntimeException("If the event method returns a value, its type must match the event interface's method. Required" + m.getReturnType() + ", found " + eventMethod.getReturnType()); } Transition transition = eventMethod.getAnnotation(Transition.class); // Fill in from here down! // Return value, no transition if (eventMethod.getReturnType() != null && transition == null) { operation = new InvokeAndTransitionOperation(metaState, eventMethod); } // Return value with transition else if (eventMethod.getReturnType() != null && transition != null) { TransitionOnSubstate[] transitions = resolveTransitions(metaState, transition.value()); operation = new InvokeAndTransitionOperation(metaState, eventMethod, transitions); } // No return value, no transition else if (eventMethod.getReturnType() == null && transition == null) { operation = new InvokeVoidAndTransitionOperation(metaState, eventMethod); } // No return value, with transition else if (eventMethod.getReturnType() == null && transition != null) { TransitionOnSubstate[] transitions = resolveTransitions(metaState, transition.value()); operation = new InvokeVoidAndTransitionOperation(metaState, eventMethod, transitions); } } metaState.eventMethods.put(m, operation); } } } private void fillInGuardMethods(MetaState metaState) { Class stateClass = metaState.stateClass; for (Method m : stateClass.getMethods()) { Guard guard = m.getAnnotation(Guard.class); if (guard != null) { if (m.getReturnType() != boolean.class) { throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.GUARD_METHOD_DOESNT_RETURN_BOOLEAN, new Object[]{m.toGenericString(), m.getReturnType()}); } TransitionOnSubstate[] transitions = resolveTransitions(metaState, guard.value()); Operation op = new InvokeGuardOperation(metaState, m, transitions); metaState.guardMethods.add(op); } } } private void fillInEntryMethods(MetaState metaState) { Class stateClass = metaState.stateClass; for (Method m : stateClass.getMethods()) { OnEntry onEntry = m.getAnnotation(OnEntry.class); if (onEntry != null) { if (m.getReturnType() != void.class) { throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.ENTRY_METHOD_ISNT_VOID, new Object[]{m.toGenericString(), m.getReturnType()}); } Operation op = new InvokeVoidAndTransitionOperation(metaState, m, new TransitionOnSubstate[0]); metaState.entryMethods.add(op); } } } private void fillInExitMethods(MetaState metaState) { Class stateClass = metaState.stateClass; for (Method m : stateClass.getMethods()) { OnExit onEntry = m.getAnnotation(OnExit.class); if (onEntry != null) { if (m.getReturnType() != void.class) { throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.EXIT_METHOD_ISNT_VOID, new Object[]{m.toGenericString(), m.getReturnType()}); } Operation op = new InvokeVoidAndTransitionOperation(metaState, m, new TransitionOnSubstate[0]); metaState.exitMethods.add(op); } } } /** * Convert an array of classes to a set of transitions with reference to a * given metastate. Transitions are specified as an array of classes on an * event method in a state class. In order to be used, they need to be * resolved to a particular substate field related to that metastate. <br> * The class referenced can be one of <ul> <li>a class that is a possible * state of one of the substates defined on the state class that contains * the annotated event method.</li> <li>A class that is a possible peer * state to the state class that contains the annotated method. </li> <li>A * class that is a possible peer state to an ancester of the state class * that contains the annotated method. </li> </ul> * * @param metaState * @param transitionClasses * @return */ private TransitionOnSubstate[] resolveTransitions(MetaState metaState, Class[] transitionClasses) { List<TransitionOnSubstate> allTransitions = new ArrayList<TransitionOnSubstate>(); for (Class c : transitionClasses) { List<TransitionOnSubstate> transitionsForClass = new ArrayList<TransitionOnSubstate>(); resolveSubstateTransitions(transitionsForClass, metaState, c); resolvePeerAndParentTransitions(transitionsForClass, metaState, c); if (transitionsForClass.isEmpty()) { throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.CANT_RESOLVE_TRANSITIONS_FOR_CLASS, new Object[]{metaState.stateClass.getName(), c.getName()}); } allTransitions.addAll(transitionsForClass); } return allTransitions.toArray(new TransitionOnSubstate[0]); } /** * Go through each of the substates and find any metastates that correspond * to the given state class. Create a transition for them and add it to the * list of transitions. * * @param transitionsForClass * @param metaState * @param c */ private void resolveSubstateTransitions(List<TransitionOnSubstate> transitionsForClass, MetaState metaState, Class c) { for (SubstateInfo substateInfo : metaState.substates) { for (MetaState m : substateInfo.getPossibleMetaStates()) { if (m.stateClass == c) { transitionsForClass.add(new TransitionOnSubstate(substateInfo, m)); } } } } private void resolvePeerAndParentTransitions(List<TransitionOnSubstate> transitionsForClass, MetaState metaState, Class c) { for (MetaState currentMetaState = metaState.parent; currentMetaState != null; currentMetaState = currentMetaState.parent) { resolveSubstateTransitions(transitionsForClass, currentMetaState, c); } } }