river-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gtra...@apache.org
Subject [37/40] Hierarchical State Machine is now broken out to its own module (river-hsm) and clears almost all findbugs/pmd/etc checks.
Date Mon, 13 Jan 2014 03:35:44 GMT
http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java b/river-hsm/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
new file mode 100644
index 0000000..0fb58a7
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
@@ -0,0 +1,429 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ */
+public class PlainStateMachineExecutor implements StateMachineExecutor, StateMachineInfo {
+
+    private static final Logger log =
+            Logger.getLogger(PlainStateMachineExecutor.class.getName(),
+            MessageNames.BUNDLE_NAME);
+
+    /**
+     * Convenience method to compile a state machine and instantiate it.
+     *
+     * @param rootStateClass
+     * @return
+     */
+    public static Object createProxy(Class rootStateClass) {
+        StateMachineCompiler compiler = new StateMachineCompiler();
+        try {
+            MetaState rootState = compiler.compile(rootStateClass);
+            instantiate(rootState);
+            return createProxy(rootState);
+        } catch (StateMachineException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_CREATING_PROXY);
+        }
+    }
+
+    /**
+     * Convenience method to compile a state machine and instantiate it.
+     *
+     * @param rootStateClass
+     * @return
+     */
+    public static Object createProxy(Object rootStateInstance) {
+        StateMachineCompiler compiler = new StateMachineCompiler();
+        try {
+            MetaState rootState = compiler.compile(rootStateInstance.getClass());
+            rootState.stateInstance = rootStateInstance;
+            instantiate(rootState);
+            return createProxy(rootState);
+        } catch (StateMachineException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_CREATING_PROXY);
+        }
+    }
+
+    /**
+     * Create a fully-instantiated metastate from a compiled metastate.
+     *
+     * @param rootState
+     */
+    public static void instantiate(MetaState rootState) {
+        /* Create a user class instance to go with every metastate. */
+        rootState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                try {
+                    if (metaState.stateInstance != null) {
+                        return;
+                    }
+                    /*
+                     * Goal here is to create a stateInstance for each metastate.
+                     * In the simple case, the stateClass is a root class, and we can
+                     * just instantiate it.
+                     */
+                    if (metaState.stateClass.getEnclosingClass() == null) {
+                        metaState.stateInstance = metaState.stateClass.newInstance();
+                    } else {
+                        /* OK, we need to get an instance of the enclosing class.
+                         * That should be the stateInstance of a parent state.
+                         */
+                        Object parentInstance = findAParentInstance(metaState, metaState.stateClass.getEnclosingClass());
+
+                        Constructor con = metaState.stateClass.getConstructor(parentInstance.getClass());
+                        metaState.stateInstance = con.newInstance(parentInstance);
+                    }
+                    log.log(Level.FINE, MessageNames.METASTATE_WAS_INSTANTIATED, new Object[]{metaState.stateClass, metaState.stateInstance});
+                } catch (Exception ex) {
+                    throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_INSTANTIATING);
+                }
+            }
+        });
+        /* Now set all the initial state instance values. */
+        rootState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                try {
+                    for (SubstateInfo ssi : metaState.substates) {
+                        ssi.setObjectThatHoldsField(metaState.stateInstance);
+                        log.log(Level.FINE, MessageNames.SETTING_FIELD_TO, new Object[]{ssi.getField().getName(), metaState.stateInstance, ssi.getInitialMetaState().stateInstance});
+                        writeStateField(ssi, metaState);
+                    }
+                } catch (Exception ex) {
+                    throw new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_INSTANTIATING);
+                }
+            }
+        });
+    }
+
+    private static void writeStateField(SubstateInfo ssi, MetaState metaState) throws IllegalArgumentException, IllegalAccessException, SecurityException {
+        boolean originalAccess = ssi.getField().isAccessible();
+        ssi.getField().setAccessible(true);
+        ssi.getField().set(ssi.getObjectThatHoldsField(), metaState.stateInstance);
+        ssi.getField().setAccessible(originalAccess);
+    }
+
+    private static Object findAParentInstance(MetaState metaState, Class enclosingClass) {
+        for (MetaState currentState = metaState.parent; currentState != null; currentState = currentState.parent) {
+            if (currentState.stateClass == enclosingClass) {
+                return currentState.stateInstance;
+            }
+        }
+        throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.NO_PARENT_INSTANCE,
+                new Object[]{enclosingClass, metaState.stateInstance});
+    }
+
+    public static Object createProxy(MetaState instantiatedMetaState) {
+        RootState rootStateAnnotation = (RootState) instantiatedMetaState.stateClass.getAnnotation(RootState.class);
+        Class[] eventInterfaces = rootStateAnnotation.value();
+        PlainStateMachineExecutor executor = new PlainStateMachineExecutor(instantiatedMetaState);
+        executor.activate();
+        Object proxy =
+                Proxy.newProxyInstance(eventInterfaces[0].getClassLoader(),
+                eventInterfaces,
+                executor.getInvocationHandler());
+        return proxy;
+    }
+    InvocationHandler invocationHandler = null;
+
+    public InvocationHandler getInvocationHandler() {
+        if (invocationHandler == null) {
+            invocationHandler = new InvocationHandler() {
+                @Override
+                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                    return runEvent(method, args);
+                }
+            };
+        }
+        return invocationHandler;
+    }
+
+    PlainStateMachineExecutor(MetaState instantiatedMetaState) {
+        this.rootMetaState = instantiatedMetaState;
+        fillInControllerReferences();
+        fillInRootStateReferences();
+    }
+    MetaState rootMetaState = null;
+
+    private void activate() {
+        List<MetaState> initialStates = buildActiveStates();
+        runEntryMethods(initialStates);
+    }
+
+    private synchronized Object runEvent(Method eventMethod, Object[] args)
+            throws Throwable {
+        clearOutput();
+        clearExceptions();
+        clearTransitions();
+        List<MetaState> initialStates = buildActiveStates();
+        runEventActions(initialStates, eventMethod, args);
+        runGuardMethods(initialStates);
+        applyTransitions(initialStates);
+        List<MetaState> finalStates = buildActiveStates();
+        List<MetaState> exiting = new ArrayList<MetaState>();
+        List<MetaState> entering = new ArrayList<MetaState>();
+        calculateStateDelta(initialStates, finalStates, exiting, entering);
+        runExitMethods(exiting);
+        runEntryMethods(entering);
+        if (hasExceptions()) {
+            throw buildOutputException();
+        }
+        if (eventMethod.getReturnType() != null) {
+            return buildOutputValue(eventMethod.getReturnType());
+        }
+        return null;
+    }
+    Object output = null;
+
+    private void clearOutput() {
+        output = null;
+    }
+
+    private Object buildOutputValue(Class returnType) {
+        if (returnType==null ) {
+            // No output expected.  If there is output, it isn't necessarily an
+            // error, so ignore it (i.e. don't throw an exception 
+            // if returnType== null and output!=null.
+            return null;
+        }
+        returnType=normalizeReturnType(returnType);
+        if (output!= null && ! returnType.isAssignableFrom(output.getClass())) {
+            throw new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.ERROR_INCOMPATIBLE_OUTPUT,
+                new Object[] { returnType, output.getClass() });
+        }
+        return output;
+    }
+    
+    /**
+     * Handle return types for primitive values.
+     * @param returnType
+     * @return 
+     */
+    private Class normalizeReturnType(Class returnType) {
+        if (returnType==int.class) {
+            return Integer.class;
+        } else if (returnType==float.class) {
+            return Float.class;
+        } else if (returnType==double.class) {
+            return Double.class;
+        } else if (returnType==long.class) {
+            return Long.class;
+        } else if (returnType==short.class) {
+            return Short.class;
+        } else if (returnType==boolean.class) {
+            return Boolean.class;
+        } else if (returnType==byte.class) {
+            return Byte.class;
+        } else if (returnType==char.class) {
+            return Character.class;
+        }
+        return returnType;
+    }
+    
+    List<Throwable> exceptions = new ArrayList<Throwable>();
+
+    private void clearExceptions() {
+        exceptions.clear();
+    }
+
+    private boolean hasExceptions() {
+        return !exceptions.isEmpty();
+    }
+
+    private Throwable buildOutputException() {
+        if (exceptions.size() == 1) {
+            return exceptions.get(0);
+        } else {
+            return new StateMachineException(MessageNames.BUNDLE_NAME, MessageNames.MULTIPLE_EXCEPTIONS_THROWN,
+                    new Object[0]);
+        }
+    }
+    List<TransitionOnSubstate> queuedTransitions = new ArrayList<TransitionOnSubstate>();
+
+    private void clearTransitions() {
+        queuedTransitions.clear();
+    }
+
+    @Override
+    public void queueTransition(TransitionOnSubstate t) {
+        log.log(Level.FINER, MessageNames.QUEUED_TRANSITION,
+                new Object[]{t.targetMetaState.stateClass.getSimpleName(),
+                    t.targetMetaState.parent.stateClass.getSimpleName()});
+        queuedTransitions.add(t);
+    }
+
+    @Override
+    public void output(Object outputObject) {
+        output = outputObject;
+    }
+
+    @Override
+    public void exception(MetaState metaState, Method interfaceMethod, Throwable cause) {
+        log.log(Level.FINE, MessageNames.STORING_EXCEPTION, new Object[]{metaState.stateInstance, interfaceMethod.getName(), cause.toString()});
+        exceptions.add(cause);
+    }
+
+    private List<MetaState> buildActiveStates() {
+        return rootMetaState.getActiveMetaStates();
+    }
+
+    private void runEventActions(List<MetaState> activeStates, Method eventInterfaceMethod, Object[] args) {
+        boolean thereWasAnEventMethod=false;
+        for (MetaState ms : activeStates) {
+            Operation op = ms.eventMethods.get(eventInterfaceMethod);
+            if (op != null) {
+                thereWasAnEventMethod=true;
+                op.eval(this, args);
+            }
+        }
+        if(!thereWasAnEventMethod) {
+            exceptions.add(new IllegalStateException());
+        }
+    }
+
+    private void runGuardMethods(List<MetaState> activeStates) {
+        for (MetaState ms : activeStates) {
+            for (Operation op : ms.guardMethods) {
+                op.eval(this, null);
+            }
+        }
+    }
+
+    private void runExitMethods(List<MetaState> exitingStates) {
+        for (MetaState ms : exitingStates) {
+            for (Operation op : ms.exitMethods) {
+                op.eval(this, null);
+            }
+        }
+    }
+
+    private void runEntryMethods(List<MetaState> enteringStates) {
+        for (MetaState ms : enteringStates) {
+            for (Operation op : ms.entryMethods) {
+                op.eval(this, null);
+            }
+        }
+    }
+
+    private void applyTransitions(List<MetaState> activeStates) {
+
+        while (!queuedTransitions.isEmpty()) {
+            /* Pull a transition. */
+            TransitionOnSubstate tos = queuedTransitions.remove(queuedTransitions.size() - 1);
+            /* Apply it. */
+            applyTransition(tos);
+            /* Add any implied transitions to the list of transitions, if the new state is not 
+             * currently active. */
+            if (!activeStates.contains(tos.targetMetaState)) {
+                queuedTransitions.addAll(tos.targetMetaState.entryTransitions);
+            }
+        }
+    }
+
+    private void applyTransition(TransitionOnSubstate tos) {
+        try {
+            tos.substate.setActiveMetaState(tos.targetMetaState);
+            writeStateField(tos.substate, tos.targetMetaState);
+            /* Get rid of this - it doesn't work on private fields.  Use the writeStateField() above.
+             tos.substate.getField().set(tos.targetMetaState.parent.stateInstance, tos.targetMetaState.stateInstance);
+             */
+        } catch (Exception ex) {
+            StateMachineException sme = new StateMachineException(ex, MessageNames.BUNDLE_NAME, MessageNames.ERROR_APPLYING_TRANSITION,
+                    new Object[]{ex.getMessage()});
+            //sme.printStackTrace();
+            throw sme;
+        }
+    }
+
+    private void calculateStateDelta(List<MetaState> initialStates, List<MetaState> finalStates, List<MetaState> exiting, List<MetaState> entering) {
+        for (MetaState initialState : initialStates) {
+            if (!finalStates.contains(initialState)) {
+                exiting.add(initialState);
+            }
+        }
+        for (MetaState finalState : finalStates) {
+            if (!initialStates.contains(finalState)) {
+                entering.add(finalState);
+            }
+        }
+    }
+
+    private void fillInControllerReferences() {
+        rootMetaState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                log.fine("Visiting " + metaState + " to fill in controller reference.");
+                fillInReferenceFields(metaState, Controller.class, PlainStateMachineExecutor.this);
+            }
+        });
+    }
+
+    private void fillInRootStateReferences() {
+        rootMetaState.visitAll(new MetaStateVisitor() {
+            @Override
+            public void visit(MetaState metaState) {
+                log.fine("Visiting " + metaState + " to fill in root state reference.");
+                fillInReferenceFields(metaState, RootState.class, rootMetaState.stateInstance);
+            }
+        });
+    }
+
+    private void fillInReferenceFields(MetaState metaState, Class<? extends Annotation> aClass, Object value) {
+
+        for (Field f : metaState.stateClass.getFields()) {
+
+            if (f.getAnnotation(aClass) != null) {
+                try {
+                    log.fine("Setting field " + metaState.stateInstance + "." + f.getName()
+                            + " to " + value);
+                    boolean accessible = f.isAccessible();
+                    f.setAccessible(true);
+                    f.set(metaState.stateInstance, value);
+                    f.setAccessible(accessible);
+                } catch (Exception ex) {
+                    throw new StateMachineException(MessageNames.BUNDLE_NAME,
+                            MessageNames.ERROR_INSTANTIATING, new Object[]{ex.getMessage()});
+
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<Class> getActiveStates() {
+        return rootMetaState.getActiveStates();
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/Retained.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/Retained.java b/river-hsm/src/main/java/org/apache/river/container/hsm/Retained.java
new file mode 100644
index 0000000..6f30ed0
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/Retained.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Annotation that indicates a state should be retained across transitions,
+ rather than reset every time the parent state is entered.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Retained {
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/RootState.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/RootState.java b/river-hsm/src/main/java/org/apache/river/container/hsm/RootState.java
new file mode 100644
index 0000000..415fbb1
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/RootState.java
@@ -0,0 +1,40 @@
+/*
+ * 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.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate the root class of a hierarchical state machine.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface RootState {
+    /**
+     The event interfaces that this machine is intended to implement.
+     @return
+     */
+    Class[] value();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/State.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/State.java b/river-hsm/src/main/java/org/apache/river/container/hsm/State.java
new file mode 100644
index 0000000..01d258b
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/State.java
@@ -0,0 +1,43 @@
+/*
+ * 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.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ Annotation type to indicate a variable that stores a state in a
+ hierarchical state machine.  To specify parallel state (AND-States as per
+ Harel), simply include more than one field marked with @State.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface State {
+    /**
+     An array of state classes that reflect the allowable states for this
+     state variable.
+     @return
+     */
+    Class[] value();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
new file mode 100644
index 0000000..8fc33da
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
@@ -0,0 +1,306 @@
+/*
+ * 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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineException.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineException.java b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineException.java
new file mode 100644
index 0000000..c88e278
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineException.java
@@ -0,0 +1,109 @@
+/*
+ * 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.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * This is a runtime exception with localized error messages.
+ */
+public class StateMachineException extends RuntimeException {
+    private final String messageBundleName;
+    private final String messageKey;
+
+    String getMessageBundleName() {
+        return messageBundleName;
+    }
+
+    String getMessageKey() {
+        return messageKey;
+    }
+
+    Object[] getMessageParameters() {
+        return messageParameters;
+    }
+    private final Object[] messageParameters;
+
+    /**
+     Construct a runtime exception with a localized message.
+     @param messageBundleName Name of the message bundle.
+     @param messageKey Message key for this message.
+     @param messageParameters Parameters if required, null or empty if not.
+     */
+    public StateMachineException(String messageBundleName,
+            String messageKey,
+            Object[] messageParameters) {
+        this.messageBundleName=messageBundleName;
+        this.messageKey=messageKey;
+        this.messageParameters=new Object[messageParameters.length];
+        System.arraycopy(messageParameters, 0, this.messageParameters, 0 , 
+                messageParameters.length);
+    }
+
+    /**
+     Construct a runtime exception with a localized message.
+     @param messageBundleName Name of the message bundle.
+     @param messageKey Message key for this message.
+     @param messageParameters Parameters if required, null or empty if not.
+     @param rootCause A root cause for this exception if available.
+     */
+    public StateMachineException(Throwable rootCause,
+            String messageBundleName,
+            String messageKey,
+            Object ... messageParameters) {
+        super(rootCause);
+        this.messageBundleName=messageBundleName;
+        this.messageKey=messageKey;
+        this.messageParameters=messageParameters;
+    }
+
+    /**
+     Localize and return the error message, according to the default Locale.
+     Note that the resolution of the resource bundle and the localization is
+     deferred until this method is called, allowing the existence of different
+     resource bundles or locales in the thrower and receiver (e.g. in the case
+     of a serialized exception passed between two JVMs.
+
+     @return The localized message.
+     */
+    @Override
+    public String getMessage() {
+        ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName);
+        String message=(String) bundle.getObject(messageKey);
+        return MessageFormat.format(message, messageParameters);
+    }
+
+    /**
+     Return the message localized using the given locale.
+     Note that the resolution of the resource bundle and the localization is
+     deferred until this method is called, allowing the existence of different
+     resource bundles or locales in the thrower and receiver (e.g. in the case
+     of a serialized exception passed between two JVMs.
+
+     @param locale The localized message
+     @return
+     */
+    public String getMessage(Locale locale) {
+        ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName, locale);
+        String message=(String) bundle.getObject(messageKey);
+        return MessageFormat.format(message, messageParameters);
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java
new file mode 100644
index 0000000..d652523
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineExecutor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.Method;
+
+/**
+ *
+ */
+public interface StateMachineExecutor {
+    
+    /**
+     * Queue a transition to a new state, which will be executed after the 
+     * current action.
+     * @param parentState
+     * @param newState 
+     */
+    void queueTransition(TransitionOnSubstate t);
+
+    void output(Object outputObject);
+
+    void exception(MetaState metaState, Method interfaceEvent, Throwable cause);
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java
new file mode 100644
index 0000000..4c65bb2
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/StateMachineInfo.java
@@ -0,0 +1,28 @@
+/*
+ * 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.util.List;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface StateMachineInfo {
+    List<Class> getActiveStates();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/SubstateInfo.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/SubstateInfo.java b/river-hsm/src/main/java/org/apache/river/container/hsm/SubstateInfo.java
new file mode 100644
index 0000000..df6a76a
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/SubstateInfo.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+/**
+ *
+ * Information on substates within a MetaState.
+ */
+class SubstateInfo {
+
+    private Field field;
+    private Object objectThatHoldsField;
+
+    public Object getObjectThatHoldsField() {
+        return objectThatHoldsField;
+    }
+
+    public void setObjectThatHoldsField(Object objectThatHoldsField) {
+        this.objectThatHoldsField = objectThatHoldsField;
+    }
+    
+    /** this field will disappear when the StateMachineCompiler is complete. */
+    private Class[] possibleStates;
+    
+    private MetaState[] possibleMetaStates;
+
+    public MetaState[] getPossibleMetaStates() {
+        return possibleMetaStates;
+    }
+
+    public void setPossibleMetaStates(MetaState[] possibleMetaStates) {
+        this.possibleMetaStates = possibleMetaStates;
+    }
+
+    public MetaState getInitialMetaState() {
+        return initialMetaState;
+    }
+
+    public void setInitialMetaState(MetaState initialMetaState) {
+        this.initialMetaState = initialMetaState;
+    }
+    
+    /** this field will disappear when the StateMachineCompiler is complete. */
+    private Class initialState;
+    
+    private MetaState initialMetaState;
+    
+    private boolean retained=false;
+    private MetaState activeMetaState;
+
+    public MetaState getActiveMetaState() {
+        return activeMetaState;
+    }
+
+    public void setActiveMetaState(MetaState activeMetaState) {
+        this.activeMetaState = activeMetaState;
+    }
+    
+    public Field getField() {
+        return field;
+    }
+
+    public void setField(Field field) {
+        this.field = field;
+    }
+
+    public Class getInitialState() {
+        return initialState;
+    }
+
+    public void setInitialState(Class initialState) {
+        this.initialState = initialState;
+    }
+
+    public Class[] getPossibleStates() {
+        return possibleStates;
+    }
+
+    public void setPossibleStates(Class[] possibleStates) {
+        this.possibleStates = possibleStates;
+    }
+
+    public boolean isRetained() {
+        return retained;
+    }
+
+    public void setRetained(boolean retained) {
+        this.retained = retained;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/Transition.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/Transition.java b/river-hsm/src/main/java/org/apache/river/container/hsm/Transition.java
new file mode 100644
index 0000000..75348f4
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/Transition.java
@@ -0,0 +1,42 @@
+/*
+ * 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.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation type to indicate an unconditional  transition that is applied after an
+ * event method is executed.  
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Transition {
+
+    /**
+     * An array of state classes that reflect the target states for this
+     * state variable.
+     *
+     * @return
+     */
+    Class[] value();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java b/river-hsm/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java
new file mode 100644
index 0000000..b66115b
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/TransitionOnSubstate.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Holds a transition on a substate.
+ */
+class TransitionOnSubstate {
+    
+    // package access, so the executor can use it directly.
+    SubstateInfo substate;
+
+    /**
+     * Create an instance specifying the target metastate for the given
+     * substate.
+     * 
+     * @param substate
+     * @param targetMetaState 
+     */
+    public TransitionOnSubstate(SubstateInfo substate, MetaState targetMetaState) {
+        this.substate = substate;
+        this.targetMetaState = targetMetaState;
+    }
+
+    public SubstateInfo getSubstate() {
+        return substate;
+    }
+
+
+    public MetaState getTargetMetaState() {
+        return targetMetaState;
+    }
+    
+    // package access, so the executor can use it directly.
+    MetaState targetMetaState;
+    
+    
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/java/org/apache/river/container/hsm/Utils.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/java/org/apache/river/container/hsm/Utils.java b/river-hsm/src/main/java/org/apache/river/container/hsm/Utils.java
new file mode 100644
index 0000000..201d8cf
--- /dev/null
+++ b/river-hsm/src/main/java/org/apache/river/container/hsm/Utils.java
@@ -0,0 +1,78 @@
+/*
+ * 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.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+
+ @author trasukg
+ */
+public class Utils {
+
+    public static String format(Object array[]) {
+        if (array == null) {
+            return "null";
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (int j = 0; j < array.length; j++) {
+            if (j != 0) {
+                sb.append(", ");
+            }
+            sb.append("'");
+            sb.append(array[j].toString());
+            sb.append("'");
+        }
+        sb.append("]");
+
+        return sb.toString();
+    }
+
+    public static String format(Properties props) {
+        if (props == null) {
+            return "null";
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (Map.Entry entry : props.entrySet()) {
+            boolean first = true;
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
+            }
+            sb.append(entry.getKey() + "=\"");
+            sb.append(entry.getValue());
+            sb.append("\"");
+        }
+        sb.append("]");
+
+        return sb.toString();
+    }
+
+    public static String stackTrace(Throwable t) {
+        StringWriter s=new StringWriter();
+        PrintWriter pw=new PrintWriter(s);
+        t.printStackTrace(pw);
+        return s.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/main/resources/org/apache/river/container/hsm/Messages.properties
----------------------------------------------------------------------
diff --git a/river-hsm/src/main/resources/org/apache/river/container/hsm/Messages.properties b/river-hsm/src/main/resources/org/apache/river/container/hsm/Messages.properties
new file mode 100644
index 0000000..3c3cd3d
--- /dev/null
+++ b/river-hsm/src/main/resources/org/apache/river/container/hsm/Messages.properties
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+applyingEventToState=Applying event {0} to state {1} instance {2}.
+beginningCompile=Begginning state machine compilation on {0}.
+compileCompleted=Completed compile on {0}.
+cantResolveTransitionsForClass=Can''t find any valid targets for transition ''{1}'' from the context of state class ''{0}''.
+entryMethodIsntVoid=Methods annotated with @OnEntry need to return void.  Method {0} returns {1}.
+errorApplyingTransition=Got an error while applying a transition: {0}.
+errorCreatingProxy=Error Creating Proxy.
+errorIncompatibleOutput=Incompatible output from state machine method: Expected {0} but got {1}.
+errorInvokingTargetMethod=Error invoking target method on state instance.
+errorInstantiating=Error while instantiating the state machine: {0}.
+exitMethodIsntVoid=Methods annotated with @OnExit need to return void.  Method {0} returns {1}.
+guardMethodDoesntReturnBoolean=Methods annotated with @Guard need to return a boolean value.  Method {0} returns {1}.
+metaStateWasInstantiated=MetaState for {0} was instantiated as {1}.
+multipleExceptionsThrown=Execution of the event threw multiple exceptions.
+noParentInstance=Couldn''t find a parent instance for {0} starting at state class {1}.
+queuedTransition=Queued transition to {0} on {1}.
+runningGuardOnState=Running guard method {0} on state {1} instance {2}.
+runningGuardTransitions=Guard method {0} on state {1} instance {2} returned true - running transitions.
+settingFieldTo=Setting field {0} on {1} to {2}.
+settingInitialSubstate=Setting initial substate field {0} on state {1}.
+storingException=Storing exception {2} thrown while executing {1} on {0}.

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/site/markdown/StateMachine.md
----------------------------------------------------------------------
diff --git a/river-hsm/src/site/markdown/StateMachine.md b/river-hsm/src/site/markdown/StateMachine.md
new file mode 100644
index 0000000..7fea657
--- /dev/null
+++ b/river-hsm/src/site/markdown/StateMachine.md
@@ -0,0 +1,63 @@
+<!--
+ 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.
+
+-->
+Some thinking on the Hierarchical State Machine
+===============================================
+
+The application deployment system includes a hierarchical state machine
+implementation that is intended to be used to help control the deployment of 
+applications.
+
+Use cases:
+
+For instance, an application exists in one of several states: Created, 
+Resolved, Starting, Started, Stopping Gracefully, Stopped, Stopping Forcefully,
+Failed to Stop.  If we have an application that started successfully, then
+we attempt to stop it, it may shut all its threads down properly, which puts it
+into the Stopped state.  Or it may fail to stop all its threads, in which case, 
+we would attempt to interrupt the thread group (Stopping Forcefully).  After a 
+while, if the thread group's active thread count didn't go to zero, it would
+enter the "Failed to Stop" state.  In this state, we would interrupt the thread
+group at regular intervals.  If the thread count happened to go to zero, it
+would go to the Stopped state, otherwise it would just sit there in the "Failed 
+to Stop" state.
+
+The model for the HSM is as follows:
+
+- events on the state machine are expressed as methods defined in the interface.
+- User sends an event by calling a method on a proxy that implements the machine.
+- An event is "run" on the current state hierarchy (top-level state and all
+active substates).
+- Each event can trigger a transition (actually one or more) by invoking a method
+that has an @Transition annotation that lists the set of states that should be
+activated after the event is run.
+- The state can have sub-states, so when we transition to a state, a sub-state
+may also become active, and will need to have its entry code run.  The sub-state
+might be a fresh state on entry, or might have been retained from the last time
+the state was active.  
+- Each state may have an "on-entry" and "on-exit" function, which will be executed
+by the transition code.  Entry and exit code 
+typically sets up timers or toggles outputs.
+- Each state can have zero or more "guard" methods, each specifying a transition.
+A guard method is run after every event is executed, and determines whether the
+transition is taken.  If the
+guard method returns true, the corresponding transition is executed.  These 
+could also be seen as "anonymous" transitions, as they are not attached to any
+particular event.
+
+

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/site/markdown/index.md
----------------------------------------------------------------------
diff --git a/river-hsm/src/site/markdown/index.md b/river-hsm/src/site/markdown/index.md
new file mode 100644
index 0000000..d19b3a4
--- /dev/null
+++ b/river-hsm/src/site/markdown/index.md
@@ -0,0 +1,36 @@
+<!--
+ 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.
+
+-->
+
+River Container Hierarchical State Machine
+==========================================
+
+Introduction
+------------
+
+A Hierarchical (or Harel) State Machine is a useful construct in many 
+asynchronous event-driven scenarios.  This library is an implementation of
+a hierarchical state machine that was designed for the application startup
+component of the Apache River Container
+but may also be useful for other things, so it's broken out into a separate 
+library.
+
+- [State Machine](StateMachine.html) Information on the annotation-based state
+machine implementation used in various places in the container.
+
+

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java b/river-hsm/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java
new file mode 100644
index 0000000..e21e0fc
--- /dev/null
+++ b/river-hsm/src/test/java/org/apache/river/container/hsm/HSMTestSuite.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed 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 org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ *
+ * @author trasukg
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({LoggingTest.class, ReturnTypeTest.class, InitializedMachineTest.class, StateMachineCompilerTest.class, PlainMachineExecutorTest.class})
+public class HSMTestSuite {
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java b/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java
new file mode 100644
index 0000000..47149fe
--- /dev/null
+++ b/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedMachineTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.logging.Level;
+import java.util.logging.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class InitializedMachineTest {
+
+    public InitializedMachineTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+        Logger.getLogger("org.apache.river.container.hsm").setLevel(Level.FINEST);
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        UUT=new InitializedTestSM();
+        UUTI=(InitializedTestSMInterface) PlainStateMachineExecutor.createProxy(UUT);
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    InitializedTestSMInterface UUTI=null;
+    InitializedTestSM UUT=null;
+    
+    @Test(expected=IllegalStateException.class)
+    public void testLockedException() throws Exception {
+        UUTI.setValue(20);
+    }
+    
+    /**
+     * If we transition to unlocked, then that means the @Transition tag
+     * was interpreted and executed correctly.
+     */
+    @Test
+    public void testUnlocking() {
+        UUTI.unlock();
+        assertTrue("lockedState is not instance of Unlocked", UUT.lockedState instanceof InitializedTestSM.Unlocked);
+        UUTI.setValue(20);
+    }
+    
+    /**
+     * The "Armed" state subclasses Locked, so the unlocking should continue to
+     * work.
+     */
+    @Test
+    public void testArming() {
+        UUTI.arm();
+        assertTrue("lockedState is not instance of Armed", UUT.lockedState instanceof InitializedTestSM.Armed);
+        
+        UUTI.unlock();
+        assertTrue("lockedState is not instance of Unlocked", UUT.lockedState instanceof InitializedTestSM.Unlocked);
+        UUTI.setValue(20);
+        
+    }
+    
+    /**
+     * Test that the methods are executing against the same instance that we
+     * created.
+     */
+    @Test
+    public void testSameInstance() {
+        UUTI.unlock();
+        UUTI.setValue(20);
+        assertEquals("Value through local instance", 20, UUT.getValue());
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java b/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java
new file mode 100644
index 0000000..4a3a80a
--- /dev/null
+++ b/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSM.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+/**
+ * State machine to test whether the state machine correctly uses the supplied
+ * root state object.
+ */
+@RootState(InitializedTestSMInterface.class)
+public class InitializedTestSM {
+
+    private int value = 0;
+
+    @State({Locked.class, Unlocked.class, Armed.class})
+    @Initial(Locked.class)
+    Object lockedState;
+
+    public int getValue() {
+        return value;
+    }
+
+    public class Locked {
+
+        @Transition(Unlocked.class)
+        public void unlock() {
+            System.out.println("Locked.unlock()");
+        }
+
+        public void setValue(int v) {
+            throw new IllegalStateException("Locked!");
+        }
+
+        @Transition(Armed.class)
+        public void arm() {
+        }
+    }
+
+    public class Armed extends Locked {
+    }
+
+    public class Unlocked {
+
+        @Transition(Locked.class)
+        public void lock() {
+            System.out.println("Unlocked.lock()");
+        }
+
+        public void setValue(int v) {
+            value = v;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java b/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java
new file mode 100644
index 0000000..a1c50a5
--- /dev/null
+++ b/river-hsm/src/test/java/org/apache/river/container/hsm/InitializedTestSMInterface.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ *
+ * Interface for test of initialized state machine.
+ */
+public interface InitializedTestSMInterface {
+    public void lock();
+    
+    public void unlock();
+    
+    public void arm();
+    
+    public void setValue(int x);
+    
+    public int getValue();
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/test/java/org/apache/river/container/hsm/LoggingTest.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/test/java/org/apache/river/container/hsm/LoggingTest.java b/river-hsm/src/test/java/org/apache/river/container/hsm/LoggingTest.java
new file mode 100644
index 0000000..8380458
--- /dev/null
+++ b/river-hsm/src/test/java/org/apache/river/container/hsm/LoggingTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.logging.Level;
+import java.util.logging.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author trasukg
+ */
+public class LoggingTest {
+
+    Logger log=Logger.getLogger(LoggingTest.class.getName());
+    
+    public LoggingTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger.getLogger("org.apache.river.container.hsm").setLevel(Level.FINER);
+        System.setProperty("java.util.logging.ConsoleHandler.level", "FINER");
+    }
+   
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+    
+    @Test
+    public void testLogging() {
+        System.out.println("Should be seeing some logging...");
+        
+        
+        log.log(Level.FINEST, "Finest");
+        log.log(Level.FINER, "Finer");
+        log.log(Level.FINE, "Fine");
+        log.log(Level.INFO, "Info");
+        log.log(Level.WARNING, "Warning");
+        log.log(Level.SEVERE, "Severe");
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/3e29cd2d/river-hsm/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java
----------------------------------------------------------------------
diff --git a/river-hsm/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java b/river-hsm/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java
new file mode 100644
index 0000000..ff5eae0
--- /dev/null
+++ b/river-hsm/src/test/java/org/apache/river/container/hsm/PlainMachineExecutorTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.util.List;
+import java.lang.reflect.InvocationTargetException;
+import java.util.logging.Logger;
+import java.lang.reflect.Field;
+import java.util.logging.Level;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ * @author trasukg
+ */
+public class PlainMachineExecutorTest {
+
+    private static final Logger log =
+            Logger.getLogger(PlainMachineExecutorTest.class.getName());
+
+    public PlainMachineExecutorTest() throws InstantiationException, IllegalAccessException,
+            NoSuchMethodException, InvocationTargetException {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger.getLogger("org.apache.river.container.hsm").setLevel(Level.FINER);
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+    TestSMInterface UUT = (TestSMInterface) PlainStateMachineExecutor.createProxy(TestSM.class);
+
+    @Test
+    /**
+     * Verify that the list of active states is correct at beginning.
+     *
+     */
+    public void testActiveStates() {
+
+        assertEquals(3, UUT.getActiveStates().size());
+        assertTrue("activeStates should contain root state.",
+                UUT.getActiveStates().contains(TestSM.class));
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertTrue("activeStates should contain A1.",
+                UUT.getActiveStates().contains(TestSM.A.A1.class));
+    }
+
+    @Test
+    /**
+     * Test that the top-level (state-invariant) sayHelloConstant() method
+     * returns the correct value. Verifies that the top-level proxy is
+     * functioning correctly.
+     */
+    public void testStateMachine() throws InstantiationException, IllegalAccessException {
+        assertEquals("Hello", UUT.sayConstantHello());
+    }
+
+    @Test
+    /**
+     * Test that the top-level (state-invariant) sayHelloConstant() method
+     * returns the correct value. Verifies that the top-level proxy is
+     * functioning correctly.
+     */
+    public void testNullReturn() throws InstantiationException, IllegalAccessException {
+        assertEquals(null, UUT.returnNull());
+    }
+
+    @Test
+    /**
+     * <p> Verify that transitions from state A to B work, and that the
+     * behaviour varies with state. </p>
+     *
+     * <p> First call to sayHello() should return "Hello", second call should
+     * return "There". </p>
+     *
+     */
+    public void testSimpleTransition() throws InstantiationException, IllegalAccessException {
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        log.info("\n\nCalling hello()\n\n");
+        assertEquals("Hello", UUT.sayHello());
+        log.info("\n\n...done\n\n");
+        List<Class> activeStates = UUT.getActiveStates();
+        assertTrue("activeStates should contain B after transition.",
+                activeStates.contains(TestSM.B.class));
+        log.info("TestSM.B appears to have been active.");
+        assertFalse("activeStates should not contain A after transition.",
+                activeStates.contains(TestSM.A.class));
+        assertEquals("There", UUT.sayHello());
+    }
+
+    @Test
+    /**
+     * When we enter a state, the entry method should be called.
+     */
+    public void testEntryMethodExecution() {
+        assertEquals(1, UUT.getAEntryCount());
+        UUT.sayHello();
+        assertEquals(1, UUT.getAExitCount());
+    }
+
+    @Test
+    /**
+     * When we transition to a state but we are already in that state, the
+     * onEntry method should not be run.
+     */
+    public void testNullTransition() {
+        UUT.nullTransition();
+        UUT.nullTransition();
+        UUT.nullTransition();
+        assertEquals(1, UUT.getNullTransitionEntryCount());
+    }
+
+    @Test
+    /**
+     * Make sure that the gotoA() and gotoB() methods cause the appropriate
+     * transitions.
+     */
+    public void testABTransitions() {
+        UUT.gotoA();
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertFalse("activeStates should not contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+        UUT.gotoB();
+
+        assertFalse("activeStates should not contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertTrue("activeStates should contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+
+        UUT.gotoA();
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertFalse("activeStates should not contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+    }
+
+    @Test
+    /**
+     * @Retained annotations should be respected, and states should be
+     * initialized on entry if the
+     * @Retained is not there.
+     */
+    public void testStateInitialization() {
+        UUT.gotoA();
+        assertTrue("activeStates should contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertFalse("activeStates should not contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+        UUT.gotoB();
+
+        assertFalse("activeStates should not contain A.",
+                UUT.getActiveStates().contains(TestSM.A.class));
+        assertTrue("activeStates should contain B.",
+                UUT.getActiveStates().contains(TestSM.B.class));
+        assertTrue("activeStates should contain B1.",
+                UUT.getActiveStates().contains(TestSM.B1.class));
+
+        UUT.moveSubstateOfB();
+        assertTrue("activeStates should contain B2.",
+                UUT.getActiveStates().contains(TestSM.B2.class));
+        UUT.gotoA();
+        /* the substate isn't marked @Retained, so should reset to initial
+         on gotoB().
+         */
+        UUT.gotoB();
+        assertTrue("activeStates should contain B1.",
+                UUT.getActiveStates().contains(TestSM.B1.class));
+
+
+    }
+
+    @Test
+    /**
+     * <p> Verify that the second interface is on the proxy and effective. </p>
+     *
+     * <p> After call to doSecondInterfaceAction(), call to sayHello() should
+     * return "HelloFromC". </p>
+     *
+     */
+    public void testSecondInterface() throws InstantiationException, IllegalAccessException {
+        log.info("\n\nCalling doSecondInterfaceAction()\n\n");
+        TestSMSecondInterface UUT2=(TestSMSecondInterface) UUT;
+        UUT2.doSecondInterfaceAction();
+        log.info("\n\n...done\n\n");
+        List<Class> activeStates = UUT.getActiveStates();
+        assertTrue("activeStates should contain C after transition.",
+                activeStates.contains(TestSM.C.class));
+        log.info("TestSM.C appears to have been active.");
+        assertFalse("activeStates should not contain A after transition.",
+                activeStates.contains(TestSM.A.class));
+        assertEquals("HelloFromC", UUT.sayHello());
+    }
+    
+    
+    /**
+     * Calling an event method that isn't implemented in the current state
+     * should throw an IllegalStateException.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testUnimplementedMethod() {
+        UUT.unimplementedMethod();
+    }
+}
\ No newline at end of file


Mime
View raw message