/* * 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(); } }