You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by gt...@apache.org on 2014/01/13 04:35:11 UTC
[04/40] Initial import of River Container project into git.
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java
new file mode 100644
index 0000000..620ccd6
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StartupDeployer.java
@@ -0,0 +1,156 @@
+/*
+ * 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.deployer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileType;
+import org.apache.river.container.ConfigurationException;
+import org.apache.river.container.Context;
+import org.apache.river.container.FileUtility;
+import org.apache.river.container.Init;
+import org.apache.river.container.Injected;
+import org.apache.river.container.InjectionStyle;
+import org.apache.river.container.MBeanRegistrar;
+import org.apache.river.container.MessageNames;
+import org.apache.river.container.Name;
+import org.apache.river.container.Utils;
+
+/**
+ *
+ * A Deployer task that deploys all the applications in a given directory when
+ * the container is started up.
+ */
+public class StartupDeployer {
+
+ private static final Logger log =
+ Logger.getLogger(StartupDeployer.class.getName(), MessageNames.BUNDLE_NAME);
+
+ private String deployDirectory = org.apache.river.container.Strings.DEFAULT_DEPLOY_DIRECTORY;
+
+ @Injected(style = InjectionStyle.BY_TYPE)
+ private FileUtility fileUtility = null;
+
+ @Injected(style = InjectionStyle.BY_TYPE)
+ private Context context;
+
+ @Injected(style = InjectionStyle.BY_TYPE)
+ private StarterServiceDeployer deployer;
+
+ @Injected(style = InjectionStyle.BY_TYPE)
+ private MBeanRegistrar mbeanRegistrar;
+
+ @Name
+ private String myName = null;
+
+ private List<ApplicationEnvironment> applicationEnvironments =
+ new ArrayList<ApplicationEnvironment>();
+
+ public String getDeployDirectory() {
+ return deployDirectory;
+ }
+
+ public void setDeployDirectory(String deployDirectory) {
+ this.deployDirectory = deployDirectory;
+ }
+
+ FileObject deploymentDirectoryFile=null;
+
+ @Init
+ public void init() {
+ try {
+ tryInitialize();
+ } catch (Throwable ex) {
+ log.log(Level.SEVERE, MessageNames.STARTUP_DEPLOYER_FAILED_INIT,
+ ex);
+ throw new ConfigurationException(ex,
+ MessageNames.STARTUP_DEPLOYER_FAILED_INIT);
+ }
+ }
+
+ private void tryInitialize() throws IOException, ParseException {
+ log.log(Level.FINE, MessageNames.STARTER_SERVICE_DEPLOYER_STARTING, myName);
+ /*
+ Establish the deployment directory.
+ */
+ deploymentDirectoryFile = fileUtility.getProfileDirectory().resolveFile(deployDirectory);
+ if (deploymentDirectoryFile == null
+ || deploymentDirectoryFile.getType() != FileType.FOLDER) {
+ log.log(Level.WARNING, MessageNames.NO_DEPLOYMENT_DIRECTORY,
+ new Object[]{deployDirectory, fileUtility.getProfileDirectory()});
+ }
+ /*
+ Go through the deployment directory looking for services to deploy.
+ */
+ List<FileObject> serviceArchives =
+ Utils.findChildrenWithSuffix(deploymentDirectoryFile,
+ org.apache.river.container.Strings.SSAR);
+ if (serviceArchives != null) {
+ log.log(Level.FINE, MessageNames.FOUND_SERVICE_ARCHIVES,
+ new Object[]{serviceArchives.size(), deployDirectory});
+ deployServiceArchives(serviceArchives);
+ } else {
+ log.log(Level.WARNING, MessageNames.FOUND_NO_SERVICE_ARCHIVES,
+ new Object[]{deployDirectory});
+
+ }
+
+ }
+
+ private void deployServiceArchives(List<FileObject> serviceArchives) {
+ /*
+ Deploy those services.
+ */
+ for (FileObject archiveFile : serviceArchives) {
+ try {
+ /* Try the archive in all the deployers to see if someone can
+ * handle it. For now there's only one.
+ */
+
+ /*
+ * Create the ApplicationEnvironment for the archive.
+ */
+ ServiceLifeCycle deployedApp=deployer.deployServiceArchive(archiveFile);
+ // Register it as an MBean
+ registerApplication(deployedApp);
+ deployedApp.start();
+ } catch (Throwable t) {
+ log.log(Level.WARNING, MessageNames.FAILED_DEPLOY_SERVICE, archiveFile.toString());
+ log.log(Level.WARNING, MessageNames.EXCEPTION_THROWN, Utils.stackTrace(t));
+ }
+ }
+ }
+
+ private void registerApplication(ServiceLifeCycle deployedApp) throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
+ Hashtable<String, String> props=new Hashtable<String,String>();
+ props.put(org.apache.river.container.Strings.NAME, deployedApp.getName());
+ ObjectName oName=new ObjectName(org.apache.river.container.Strings.CONTAINER_JMX_DOMAIN, props);
+ mbeanRegistrar.getMbeanServer().registerMBean(
+ deployedApp, oName);
+ }
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java
new file mode 100644
index 0000000..d9e0269
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StatusEvents.java
@@ -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.
+ */
+package org.apache.river.container.deployer;
+
+/**
+ *
+ * @author trasukg
+ */
+interface StatusEvents {
+ public void prepareSucceeded();
+
+ public void startSucceeded();
+
+ public void stopSucceeded();
+
+ public void stopFailed();
+
+ public void exception(Exception ex);
+
+ public void timeout();
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java b/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java
new file mode 100644
index 0000000..3f48d84
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/Strings.java
@@ -0,0 +1,31 @@
+/*
+ * 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.deployer;
+
+/**
+ *
+ * @author trasukg
+ */
+public class Strings {
+ public static final String
+ EMPTY="",
+ LPAREN="(",
+ RPAREN=")",
+ SPACE=" ";
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java
new file mode 100644
index 0000000..b0c7a14
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParser.java
@@ -0,0 +1,35 @@
+/*
+ * 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.el;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface ArgsParser {
+
+ /**
+ * Take a command line as one string and break it into a set
+ * of arguments as expected by main(String[] args).
+ *
+ * @param input
+ * @return
+ */
+ String[] toArgs(String input);
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java
new file mode 100644
index 0000000..2e7c1fe
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/el/ArgsParserImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.el;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ *
+ * @author trasukg
+ */
+public class ArgsParserImpl implements ArgsParser {
+ /**
+ Take a command line as one string and break it into a set
+ of arguments as expected by main(String[] args).
+
+ @param input
+ @return
+ */
+ @Override
+ public String[] toArgs(String input) {
+ List<String> args=new ArrayList<String>();
+ StringTokenizer tok=new StringTokenizer(input," ");
+ while(tok.hasMoreTokens()) {
+ args.add(tok.nextToken());
+ }
+ return args.toArray(new String[0]);
+ }
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java
new file mode 100644
index 0000000..4e8d546
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Controller.java
@@ -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.
+ */
+
+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;
+
+/**
+ *
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Controller {
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.java
new file mode 100644
index 0000000..f495f51
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Guard.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 guard method. The annotated method must return
+ * a boolean value. The method is run after the action events are run. If the
+ * method returns true, the indicated transition is taken.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Guard {
+
+ /**
+ * 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/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.java
new file mode 100644
index 0000000..4d59888
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Initial.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 initial value of a field annotated with @State.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Initial {
+ /**
+ The initial state value to be assigned to the state upon machine setup.
+ @return
+ */
+ Class value();
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java
new file mode 100644
index 0000000..ede969c
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeAndTransitionOperation.java
@@ -0,0 +1,80 @@
+/*
+ * 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.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * State machine operation that invokes a method on the target instance
+ * and then queues a transition (or set of transitions).
+ */
+public class InvokeAndTransitionOperation implements Operation {
+
+ private static final Logger log=
+ Logger.getLogger(InvokeAndTransitionOperation.class.getName(),MessageNames.BUNDLE_NAME);
+
+
+ Method targetMethod;
+
+ /**
+ * Create an instance that executes an event method that returns a value, but
+ * does not include any transitions.
+ * @param targetMetaState
+ * @param targetMethod
+ */
+ public InvokeAndTransitionOperation(MetaState targetMetaState, Method targetMethod) {
+ this(targetMetaState, targetMethod, new TransitionOnSubstate[0]);
+ }
+
+ public InvokeAndTransitionOperation(MetaState targetMetaState, Method targetMethod, TransitionOnSubstate[] transitions) {
+ this.targetMethod = targetMethod;
+ this.targetMetaState = targetMetaState;
+ this.transitions = transitions;
+ }
+ MetaState targetMetaState;
+ TransitionOnSubstate[] transitions;
+
+ @Override
+ public void eval(StateMachineExecutor exec, Object[] args) {
+ try {
+ if (log.isLoggable(Level.FINER)) {
+ log.log(Level.FINER, MessageNames.APPLYING_EVENT_TO_STATE,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ }
+ exec.output(targetMethod.invoke(targetMetaState.stateInstance, args));
+ } catch(InvocationTargetException e) {
+ exec.exception(targetMetaState, targetMethod, e.getCause());
+ } catch (Exception ex) {
+ log.log(Level.SEVERE, MessageNames.APPLYING_EVENT_TO_STATE,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ log.log(Level.SEVERE, MessageNames.ERROR_INVOKING_TARGET_METHOD, ex);
+ }
+ // Execute the transitions
+ for (TransitionOnSubstate t:transitions) {
+ exec.queueTransition(t);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java
new file mode 100644
index 0000000..4566f89
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeGuardOperation.java
@@ -0,0 +1,87 @@
+/*
+ * 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.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * State machine operation that invokes a method on the target instance and then
+ * queues a transition (or set of transitions).
+ */
+public class InvokeGuardOperation implements Operation {
+
+ private static final Logger log =
+ Logger.getLogger(InvokeGuardOperation.class.getName(), MessageNames.BUNDLE_NAME);
+ Method targetMethod;
+
+ /**
+ * Create an instance that executes an event method that returns a value,
+ * but does not include any transitions.
+ *
+ * @param targetMetaState
+ * @param targetMethod
+ */
+ public InvokeGuardOperation(MetaState targetMetaState, Method targetMethod) {
+ this(targetMetaState, targetMethod, new TransitionOnSubstate[0]);
+ }
+
+ public InvokeGuardOperation(MetaState targetMetaState, Method targetMethod, TransitionOnSubstate[] transitions) {
+ this.targetMethod = targetMethod;
+ this.targetMetaState = targetMetaState;
+ this.transitions = transitions;
+ }
+ MetaState targetMetaState;
+ TransitionOnSubstate[] transitions;
+
+ @Override
+ public void eval(StateMachineExecutor exec, Object[] args) {
+ try {
+ if (log.isLoggable(Level.FINER)) {
+ log.log(Level.FINER, MessageNames.RUNNING_GUARD_ON_STATE,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ }
+ Boolean b = (Boolean) targetMethod.invoke(targetMetaState.stateInstance, args);
+ if (b.booleanValue()) {
+ // Execute the transitions
+ if (log.isLoggable(Level.FINER)) {
+ log.log(Level.FINER, MessageNames.RUNNING_GUARD_TRANSITIONS,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ }
+
+ for (TransitionOnSubstate t : transitions) {
+ exec.queueTransition(t);
+ }
+ }
+ } catch (InvocationTargetException e) {
+ exec.exception(targetMetaState, targetMethod, e.getCause());
+ } catch (Exception ex) {
+ log.log(Level.SEVERE, MessageNames.RUNNING_GUARD_ON_STATE,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ log.log(Level.SEVERE, MessageNames.ERROR_INVOKING_TARGET_METHOD, ex);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java
new file mode 100644
index 0000000..cc74176
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/InvokeVoidAndTransitionOperation.java
@@ -0,0 +1,80 @@
+/*
+ * 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.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * State machine operation that invokes a method on the target instance
+ * and then queues a transition (or set of transitions).
+ */
+public class InvokeVoidAndTransitionOperation implements Operation {
+
+ private static final Logger log=
+ Logger.getLogger(InvokeVoidAndTransitionOperation.class.getName(),MessageNames.BUNDLE_NAME);
+
+
+ Method targetMethod;
+
+ /**
+ * Create an instance that executes an event method that returns a value, but
+ * does not include any transitions.
+ * @param targetMetaState
+ * @param targetMethod
+ */
+ public InvokeVoidAndTransitionOperation(MetaState targetMetaState, Method targetMethod) {
+ this(targetMetaState, targetMethod, new TransitionOnSubstate[0]);
+ }
+
+ public InvokeVoidAndTransitionOperation(MetaState targetMetaState, Method targetMethod, TransitionOnSubstate[] transitions) {
+ this.targetMethod = targetMethod;
+ this.targetMetaState = targetMetaState;
+ this.transitions = transitions;
+ }
+ MetaState targetMetaState;
+ TransitionOnSubstate[] transitions;
+
+ @Override
+ public void eval(StateMachineExecutor exec, Object[] args) {
+ try {
+ if (log.isLoggable(Level.FINER)) {
+ log.log(Level.FINER, MessageNames.APPLYING_EVENT_TO_STATE,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ }
+ targetMethod.invoke(targetMetaState.stateInstance, args);
+ } catch(InvocationTargetException e) {
+ exec.exception(targetMetaState, targetMethod, e.getCause());
+ } catch (Exception ex) {
+ log.log(Level.SEVERE, MessageNames.APPLYING_EVENT_TO_STATE,
+ new Object[]{targetMethod.getName(),
+ targetMetaState.stateClass.getSimpleName(),
+ targetMetaState.stateInstance});
+ log.log(Level.SEVERE, MessageNames.ERROR_INVOKING_TARGET_METHOD, ex);
+ }
+ // Execute the transitions
+ for (TransitionOnSubstate t:transitions) {
+ exec.queueTransition(t);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java b/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java
new file mode 100644
index 0000000..bbc82c7
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/MessageNames.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * Message names for the state machine implementation's logging.
+ */
+public class MessageNames {
+
+ public static final String BUNDLE_NAME = "org.apache.river.container.hsm.Messages";
+ public static final String APPLYING_EVENT_TO_STATE = "applyingEventToState",
+ BEGINNING_COMPILE="beginningCompile",
+ CANT_RESOLVE_TRANSITIONS_FOR_CLASS = "cantResolveTransitionsForClass",
+ COMPILE_COMPLETED="compileCompleted",
+ ENTRY_METHOD_ISNT_VOID = "entryMethodIsntVoid",
+ ERROR_APPLYING_TRANSITION="errorApplyingTransition",
+ ERROR_CREATING_PROXY="errorCreatingProxy",
+ ERROR_INSTANTIATING="errorInstantiating",
+ ERROR_INVOKING_TARGET_METHOD = "errorInvokingTargetMethod",
+ EXIT_METHOD_ISNT_VOID = "exitMethodIsntVoid",
+ GUARD_METHOD_DOESNT_RETURN_BOOLEAN = "guardMethodDoesntReturnBoolean",
+ METASTATE_WAS_INSTANTIATED="metaStateWasInstantiated",
+ MULTIPLE_EXCEPTIONS_THROWN="multipleExceptionsThrown",
+ NO_PARENT_INSTANCE="noParentInstance",
+ QUEUED_TRANSITION="queuedTransition",
+ RUNNING_GUARD_ON_STATE = "runningGuardOnState",
+ RUNNING_GUARD_TRANSITIONS="runningGuardTransitions",
+ SETTING_INITIAL_SUBSTATE="settingInitialSubstate",
+ SETTING_FIELD_TO="settingFieldTo",
+ STORING_EXCEPTION="storingException";
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java
new file mode 100644
index 0000000..4d8eb6e
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaState.java
@@ -0,0 +1,106 @@
+/*
+ * 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;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An object that corresponds to a state instance.
+ */
+public class MetaState {
+
+ Object stateInstance=null;
+ Class stateClass=null;
+ MetaState parent=null;
+
+ public List<MetaState> getActiveMetaStates() {
+ List<MetaState> activeStates=new ArrayList<MetaState>();
+ getActiveMetaStates(activeStates);
+ return activeStates;
+ }
+
+ public void getActiveMetaStates(List<MetaState> activeStates) {
+ /* Add my stateInstance's class. */
+ activeStates.add(this);
+ /* Iterate down for each substate. */
+ for(SubstateInfo substate: substates) {
+ substate.getActiveMetaState().getActiveMetaStates(activeStates);
+ }
+ }
+
+ public List<Class> getActiveStates() {
+ List<MetaState> activeStates=getActiveMetaStates();
+ List<Class> activeStateClasses=new ArrayList(activeStates.size());
+ for(MetaState ms: activeStates) {
+ activeStateClasses.add(ms.stateClass);
+ }
+ return activeStateClasses;
+ }
+ List<SubstateInfo> substates=new ArrayList<SubstateInfo>();
+
+ Map<Method, Operation> eventMethods=new HashMap<Method, Operation>();
+
+ public void visitAll(MetaStateVisitor visitor) {
+ visitor.visit(this);
+ for (SubstateInfo substateInfo: substates) {
+ for (MetaState ms: substateInfo.getPossibleMetaStates()) {
+ ms.visitAll(visitor);
+ }
+ }
+ }
+
+ List<Operation> guardMethods=new ArrayList<Operation>();
+
+ List<Operation> entryMethods=new ArrayList<Operation>();
+
+ List<Operation> exitMethods=new ArrayList<Operation>();
+
+ List<TransitionOnSubstate> entryTransitions=new ArrayList<TransitionOnSubstate>();
+
+ public String toString() {
+ return stateClass==null?"Uninitialized metastate":("Meta-" + stateClass.getName());
+ }
+
+ /**
+ * Return a string representation of the possible state structure. Mainly for unit-testing.
+ * @return
+ */
+ public String getStateStructure() {
+ StringBuilder sb=new StringBuilder();
+ sb.append(stateClass.getSimpleName());
+ if (substates.size() != 0) {
+ sb.append("(");
+ for (SubstateInfo si:substates) {
+ sb.append(si.getField().getName());
+ sb.append("(");
+ for (MetaState ms: si.getPossibleMetaStates()) {
+ sb.append(ms.getStateStructure());
+ sb.append(" ");
+ }
+ sb.append(") ");
+ }
+ sb.append(") ");
+ }
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java
new file mode 100644
index 0000000..9fbda12
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/MetaStateVisitor.java
@@ -0,0 +1,13 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.river.container.hsm;
+
+/**
+ *
+ * @author trasukg
+ */
+public interface MetaStateVisitor {
+ public void visit(MetaState metaState);
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java b/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java
new file mode 100644
index 0000000..179b2fb
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/OnEntry.java
@@ -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.
+ */
+
+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;
+
+/**
+ Indicates that the annotated method should be invoked whenever the state is
+ entered. There is no guarantee on the order of execution of @OnEntry methods,
+ only that the exit methods are invoked before the entry methods for the next
+ state.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnEntry {
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java b/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java
new file mode 100644
index 0000000..7a3de76
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/OnExit.java
@@ -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.
+ */
+
+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;
+
+/**
+ Indicates that the annotated method should be invoked whenever the state is
+ exited. There is no guarantee on the order of execution of @OnExit methods,
+ only that the exit methods are invoked before the entry methods for the next
+ state.
+ * @author trasukg
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnExit {
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.java
new file mode 100644
index 0000000..ece298e
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/Operation.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 an object that evaluates "something" against a set of arguments.
+ * @author trasukg
+ */
+interface Operation {
+
+ /**
+ Run the operation against a set of arguments.
+ @param args
+ @return
+ */
+ void eval(StateMachineExecutor exec, Object[] args);
+
+}
http://git-wip-us.apache.org/repos/asf/river-container/blob/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java b/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
new file mode 100644
index 0000000..971ca6e
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/PlainStateMachineExecutor.java
@@ -0,0 +1,391 @@
+/*
+ * 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) {
+ return output;
+ }
+ 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/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java b/river-container-core/src/main/java/org/apache/river/container/hsm/Retained.java
new file mode 100644
index 0000000..6f30ed0
--- /dev/null
+++ b/river-container-core/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/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java b/river-container-core/src/main/java/org/apache/river/container/hsm/RootState.java
new file mode 100644
index 0000000..415fbb1
--- /dev/null
+++ b/river-container-core/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/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/State.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/State.java b/river-container-core/src/main/java/org/apache/river/container/hsm/State.java
new file mode 100644
index 0000000..01d258b
--- /dev/null
+++ b/river-container-core/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/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
new file mode 100644
index 0000000..8597424
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineCompiler.java
@@ -0,0 +1,321 @@
+/*
+ * 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.Constructor;
+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;
+import org.apache.river.container.Utils;
+
+/**
+ * Compiler for state machine instances. The "input" to the compiler is actually
+ * a class that is annotated with the
+ *
+ * @RootState annotation.
+ *
+ */
+public 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.
+ */
+ public Class[] getEventInterfaces() {
+ return eventInterfaces;
+ }
+
+ public 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;
+
+ }
+
+ /**
+ * Have a feeling this will end up in the StateMachineRunner
+ */
+ private Object findInstance(MetaState parentMetaState, Class enclosingClass) {
+ MetaState currentState = parentMetaState;
+ while (currentState != null) {
+ if (currentState.stateClass == enclosingClass) {
+ return currentState.stateInstance;
+ }
+ currentState = currentState.parent;
+ }
+ return null;
+ }
+
+ 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.
+ }
+ 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/9ab01093/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java
----------------------------------------------------------------------
diff --git a/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java
new file mode 100644
index 0000000..85b04b9
--- /dev/null
+++ b/river-container-core/src/main/java/org/apache/river/container/hsm/StateMachineException.java
@@ -0,0 +1,106 @@
+/*
+ * 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 String messageBundleName;
+ private String messageKey;
+
+ public String getMessageBundleName() {
+ return messageBundleName;
+ }
+
+ public String getMessageKey() {
+ return messageKey;
+ }
+
+ public Object[] getMessageParameters() {
+ return messageParameters;
+ }
+ private Object[] messageParameters;
+
+ /**
+ Construct a runtime exception with a localized message.
+ @param messageBundleName
+ @param messageKey
+ @param messageParameters
+ */
+ public StateMachineException(String messageBundleName,
+ String messageKey,
+ Object[] messageParameters) {
+ this.messageBundleName=messageBundleName;
+ this.messageKey=messageKey;
+ this.messageParameters=messageParameters;
+ }
+
+ /**
+ Construct a runtime exception with a localized message.
+ @param messageBundleName
+ @param messageKey
+ @param messageParameters
+ */
+ 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
+ @return
+ */
+ public String getMessage(Locale locale) {
+ ResourceBundle bundle=ResourceBundle.getBundle(messageBundleName, locale);
+ String message=(String) bundle.getObject(messageKey);
+ return MessageFormat.format(message, messageParameters);
+ }
+}