Skip to Content.
Sympa Menu

notify-dpml - svn commit: r2304 - in development/main: metro metro/composition/control/src/main/net/dpml/composition/control metro/composition/control/src/main/net/dpml/composition/models metro/composition/testing/activity/src/main/net/dpml/test metro/composition/testing/activity/src/test/net/dpml/test metro/state metro/state/api metro/state/api/src/main/net/dpml/state metro/state/impl metro/state/impl/src/main/net/dpml/state/impl metro/state/impl/src/test metro/state/impl/src/test/net metro/state/impl/src/test/net/dpml metro/state/impl/src/test/net/dpml/state metro/state/impl/src/test/net/dpml/state/impl transit/core/handler/src/main/net/dpml/lang

notify-dpml AT lists.ibiblio.org

Subject: DPML Notify

List archive

Chronological Thread  
  • From: mcconnell AT dpml.net
  • To: notify-dpml AT lists.ibiblio.org
  • Subject: svn commit: r2304 - in development/main: metro metro/composition/control/src/main/net/dpml/composition/control metro/composition/control/src/main/net/dpml/composition/models metro/composition/testing/activity/src/main/net/dpml/test metro/composition/testing/activity/src/test/net/dpml/test metro/state metro/state/api metro/state/api/src/main/net/dpml/state metro/state/impl metro/state/impl/src/main/net/dpml/state/impl metro/state/impl/src/test metro/state/impl/src/test/net metro/state/impl/src/test/net/dpml metro/state/impl/src/test/net/dpml/state metro/state/impl/src/test/net/dpml/state/impl transit/core/handler/src/main/net/dpml/lang
  • Date: Mon, 18 Apr 2005 11:16:59 -0400

Author: mcconnell AT dpml.net
Date: Mon Apr 18 11:16:57 2005
New Revision: 2304

Added:

development/main/metro/state/api/src/main/net/dpml/state/DuplicateKeyException.java

development/main/metro/state/api/src/main/net/dpml/state/NoSuchStateException.java
development/main/metro/state/api/src/main/net/dpml/state/Transition.java
development/main/metro/state/build.xml

development/main/metro/state/impl/src/main/net/dpml/state/impl/AbstractStateManager.java

development/main/metro/state/impl/src/main/net/dpml/state/impl/DefaultState.java

development/main/metro/state/impl/src/main/net/dpml/state/impl/DefaultTransition.java

development/main/metro/state/impl/src/main/net/dpml/state/impl/NoSuchHandlerException.java
development/main/metro/state/impl/src/test/
development/main/metro/state/impl/src/test/net/
development/main/metro/state/impl/src/test/net/dpml/
development/main/metro/state/impl/src/test/net/dpml/state/
development/main/metro/state/impl/src/test/net/dpml/state/impl/

development/main/metro/state/impl/src/test/net/dpml/state/impl/DefaultStateTestCase.java

development/main/transit/core/handler/src/main/net/dpml/lang/DefaultInvocationHandler.java

development/main/transit/core/handler/src/main/net/dpml/lang/DelegationRuntimeException.java
Removed:

development/main/metro/composition/control/src/main/net/dpml/composition/control/StateInvocationHandler.java
Modified:

development/main/metro/composition/control/src/main/net/dpml/composition/control/LifecycleHandler.java

development/main/metro/composition/control/src/main/net/dpml/composition/control/StateManagerInvocationHandler.java

development/main/metro/composition/control/src/main/net/dpml/composition/models/CompositionModel.java

development/main/metro/composition/testing/activity/src/main/net/dpml/test/Component.java

development/main/metro/composition/testing/activity/src/main/net/dpml/test/Container.java

development/main/metro/composition/testing/activity/src/test/net/dpml/test/ComponentTestCase.java
development/main/metro/index.xml
development/main/metro/state/api/ (props changed)
development/main/metro/state/api/src/main/net/dpml/state/State.java
development/main/metro/state/api/src/main/net/dpml/state/StateManager.java
development/main/metro/state/impl/ (props changed)
Log:
Add an AbstractStateManager, DefaultState and DefaultTransition and update
examples and testcases accordingly.

Modified:
development/main/metro/composition/control/src/main/net/dpml/composition/control/LifecycleHandler.java
==============================================================================
---
development/main/metro/composition/control/src/main/net/dpml/composition/control/LifecycleHandler.java
(original)
+++
development/main/metro/composition/control/src/main/net/dpml/composition/control/LifecycleHandler.java
Mon Apr 18 11:16:57 2005
@@ -38,8 +38,13 @@

import net.dpml.logging.Logger;

+import net.dpml.configuration.Configuration;
+import net.dpml.parameters.Parameters;
+
import net.dpml.system.SystemContext;

+import net.dpml.lang.DefaultInvocationHandler;
+
/**
* A lifestyle handler provides support for the aquisition and release
* of component instances. An implementation is responsible for the
@@ -126,6 +131,16 @@
{
args[i] = newContextInvocationHandler( holder, context );
}
+ else if( Configuration.class.isAssignableFrom( c ) )
+ {
+ Configuration config = model.getConfiguration();
+ args[i] = newConfigurationInvocationHandler( config );
+ }
+ else if( Parameters.class.isAssignableFrom( c ) )
+ {
+ Parameters params = model.getParameters();
+ args[i] = newParametersInvocationHandler( params );
+ }
else
{
final String error =
@@ -288,6 +303,38 @@
}
}

+ private Object newParametersInvocationHandler( Parameters params )
throws LifecycleRuntimeException
+ {
+ try
+ {
+ InvocationHandler handler = new DefaultInvocationHandler( params
);
+ ClassLoader classloader = params.getClass().getClassLoader();
+ return Proxy.newProxyInstance( classloader, new Class[]{
Parameters.class }, handler );
+ }
+ catch( Throwable e )
+ {
+ final String error =
+ "Unexpected error while attempting to construct the
configuration invocation handler.";
+ throw new LifecycleRuntimeException( error, e );
+ }
+ }
+
+ private Object newConfigurationInvocationHandler( Configuration config )
throws LifecycleRuntimeException
+ {
+ try
+ {
+ InvocationHandler handler = new DefaultInvocationHandler( config
);
+ ClassLoader classloader = config.getClass().getClassLoader();
+ return Proxy.newProxyInstance( classloader, new Class[]{
Configuration.class }, handler );
+ }
+ catch( Throwable e )
+ {
+ final String error =
+ "Unexpected error while attempting to construct the
configuration invocation handler.";
+ throw new LifecycleRuntimeException( error, e );
+ }
+ }
+
private Object newContextInvocationHandler( Holder holder, Class clazz )
throws LifecycleRuntimeException
{
try

Modified:
development/main/metro/composition/control/src/main/net/dpml/composition/control/StateManagerInvocationHandler.java
==============================================================================
---
development/main/metro/composition/control/src/main/net/dpml/composition/control/StateManagerInvocationHandler.java
(original)
+++
development/main/metro/composition/control/src/main/net/dpml/composition/control/StateManagerInvocationHandler.java
Mon Apr 18 11:16:57 2005
@@ -36,11 +36,13 @@

import net.dpml.state.State;
import net.dpml.state.StateManager;
+import net.dpml.state.Transition;
import net.dpml.state.NoSuchTransitionException;

+import net.dpml.lang.DelegationRuntimeException;
+
/**
- * This makes a dynamic proxy for an object. The object can be represented
- * by one, some or all of it's interfaces.
+ * This makes a dynamic proxy for an state manager.
*
* @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
* @version $Id: ApplianceInvocationHandler.java 2106 2005-03-21 18:46:10Z
mcconnell AT dpml.net $
@@ -69,21 +71,13 @@
{
m_holder = holder;
m_control = holder.getProcessController();
- if( null == m_control )
- {
- m_class = null;
- }
- else
- {
- m_class = m_control.getClass();
- }
+ m_class = m_control.getClass();
}

//-------------------------------------------------------------------
// implementation
//-------------------------------------------------------------------

-
/**
* Invoke the specified method on underlying object.
* This is called by the proxy object.
@@ -101,107 +95,61 @@
{
String name = method.getName();
Class source = method.getDeclaringClass();
- Class control = getControlClass();
if( Object.class == method.getDeclaringClass() )
{
- return method.invoke( this, args );
+ return method.invoke( m_control, args );
}
- else if( "initialize".equals( name ) )
+
+ if( name.equals( "terminate" ) || name.equals( "terminate" ) )
{
- Method initialize = getInitializeMethod();
- if( null != initialize )
+ try
{
- return initialize.invoke( m_control, new Object[0] );
+ Method target = m_class.getMethod( name, new Class[0] );
+ return target.invoke( m_control, args );
}
- else
+ catch( NoSuchMethodException e )
{
return null;
}
- }
- else if( "terminate".equals( name ) )
- {
- Method terminate = getTerminateMethod();
- if( null != terminate )
+ catch( Throwable e )
{
- return terminate.invoke( m_control, new Object[0] );
- }
- else
- {
- return null;
+ throw handleInvocationThrowable( method, e );
}
}
- else if( "getState".equals( name ) )
+ else if( name.equals( "getState" ) )
{
- Object state = getStateObject();
- if( null != state )
+ try
{
- Class c = state.getClass();
- if( State.class.isAssignableFrom( c ) )
- {
- return state;
- }
- else
- {
- StateInvocationHandler handler =
- new StateInvocationHandler( m_holder, state );
- ClassLoader classloader =
m_holder.getModel().getClassLoader();
- return Proxy.newProxyInstance( classloader, new Class[]{
State.class }, handler );
- }
+ Method target = m_class.getMethod( name, new Class[0] );
+ return method.invoke( m_control, args );
}
- else
+ catch( NoSuchMethodException e )
{
return new Available();
}
+ catch( Throwable e )
+ {
+ throw handleInvocationThrowable( method, e );
+ }
}
else if( "apply".equals( name ) )
{
- if( args.length < 1 )
- {
- final String error =
- "Insuffient arguments to 'apply' operation."
- + "\nMethod: " + name
- + "\nArgs: " + args.length
- + "\nInstance: " + m_holder.getURI();
- throw new IllegalArgumentException( error );
- }
-
- Object arg = args[0];
-
- if( false == ( arg instanceof String ) )
+ try
{
- final String error =
- "Transition argument is not a string."
- + "\nMethod: " + name
- + "\nInvalid Argument: " + arg.getClass().getName()
- + "\nInstance: " + m_holder.getURI();
- throw new IllegalArgumentException( error );
+ Method target = m_class.getMethod( name, new Class[]{
String.class } );
+ return method.invoke( m_control, args );
}
-
- String key = (String) arg;
- Object state = getStateObject();
-System.out.println( "# getState() return null in " + m_class );
- if( null != state )
+ catch( NoSuchMethodException e )
{
final String error =
- "Cannot appply a transition to a stateless instance."
- + "\nTransition: " + key
- + "\nInstance: " + m_holder.getURI();
- throw new IllegalArgumentException( error );
- }
- else if( args.length > 1 )
- {
- final String error =
- "Multiarg apply not supported."
- + "\nTransition: " + key
+ "The apply method is not implemented."
+ + "\nOperation: " + name
+ "\nInstance: " + m_holder.getURI();
throw new UnsupportedOperationException( error );
}
- else
+ catch( Throwable e )
{
- Class stateClass = state.getClass();
- Class type = m_holder.getModel().getDeploymentClass();
- Method m = StateInvocationHandler.getTransitionMethod( type,
stateClass, key );
- return m.invoke( state, new Object[0] );
+ throw handleInvocationThrowable( method, e );
}
}
else
@@ -214,84 +162,72 @@
}
}

- private Class getControlClass()
+ private Throwable handleInvocationThrowable( Method method, Throwable e )
{
- return m_class;
- }
+ final String name = method.getName();
+ final String error =
+ "Unexpected error while attempting to invoke a state manager
operation."
+ + "\nMethod: " + name
+ + "\nInstance: " + m_holder.getURI();

- private Method getInitializeMethod()
- {
- return getMethod( "initialize" );
- }
-
- private Method getTerminateMethod()
- {
- return getMethod( "terminate" );
- }
-
- private Method getMethod( String name )
- {
- Class base = getControlClass();
- if( null == base )
- {
- return null;
- }
- else
+ while( true )
{
- return getMethod( base, name );
+ if( e instanceof UndeclaredThrowableException )
+ {
+ Throwable cause =
+ ((UndeclaredThrowableException)e).getUndeclaredThrowable();
+ if( cause == null )
+ {
+ return new DelegationRuntimeException( error, e );
+ }
+ else
+ {
+ e = cause;
+ }
+ }
+ else if( e instanceof InvocationTargetException )
+ {
+ Throwable cause =
+ ((InvocationTargetException)e).getTargetException();
+ if( cause == null )
+ {
+ return new DelegationRuntimeException( error, e );
+ }
+ else
+ {
+ e = cause;
+ }
+ }
+ else
+ {
+ break;
+ }
}
+ return e;
}

- private Method getMethod( Class base, String name )
+
+ public static class Available implements State
{
- if( null == base )
- {
- throw new NullPointerException( "base" );
- }
- if( null == name )
- {
- throw new NullPointerException( "name" );
- }
- try
- {
- return base.getMethod( name, new Class[0] );
- }
- catch( NoSuchMethodException e )
+ public String getName()
{
- return null;
+ return "available";
}
- }
-
- private Object getStateObject() throws Exception
- {
- Method getStateMethod = getStateMethod();
- if( null == getStateMethod )
+ public String[] getTransitionNames()
{
-System.out.println( "Missing getState() method on controller class: " +
m_class );
- return null;
+ return new String[0];
}
- else
+ public Transition getTransition( String key )
{
-System.out.println( "Invoking getState(): " + m_class );
- return getStateMethod.invoke( m_control, new Object[0] );
+ throw new NoSuchTransitionException( key );
}
- }
-
- private Method getStateMethod()
- {
- return getMethod( "getState" );
- }
-
-
- public static class Available implements State
- {
- public String getName()
+ public String list()
{
- return "available";
+ return "state:available";
}
- public String[] getTransitions()
+ public boolean isTerminal()
{
- return new String[0];
+ return true;
}
}

@@ -326,9 +262,9 @@
return AVALIABLE;
}

- public State apply( String transition )
+ public void apply( String key )
{
- throw new NoSuchTransitionException( transition );
+ throw new NoSuchTransitionException( key );
}

public void terminate()
@@ -345,17 +281,6 @@
// TODO: we need a monitor down here
}
}
- if( Disposable.class.isAssignableFrom( c ) )
- {
- try
- {
- ((Disposable)m_instance).dispose();
- }
- catch( Throwable e )
- {
- // TODO: we need a monitor down here
- }
- }
}
}
}

Modified:
development/main/metro/composition/control/src/main/net/dpml/composition/models/CompositionModel.java
==============================================================================
---
development/main/metro/composition/control/src/main/net/dpml/composition/models/CompositionModel.java
(original)
+++
development/main/metro/composition/control/src/main/net/dpml/composition/models/CompositionModel.java
Mon Apr 18 11:16:57 2005
@@ -48,6 +48,12 @@

import net.dpml.composition.control.CompositionController;

+import net.dpml.configuration.Configuration;
+import net.dpml.configuration.impl.DefaultConfiguration;
+
+import net.dpml.parameters.Parameters;
+import net.dpml.parameters.impl.DefaultParameters;
+
import net.dpml.meta.info.Type;
import net.dpml.meta.info.ServiceDescriptor;
import net.dpml.meta.info.DependencyDescriptor;
@@ -76,6 +82,9 @@
private final String m_lifestyle;
private final URI m_uri;

+ private Configuration m_configuration;
+ private Parameters m_parameters;
+
/**
* Creation of a new root component model.
*
@@ -161,6 +170,13 @@
m_lifestyle = profile.getLifestylePolicy();

//
+ // set configuration and parameters form the supplied profile
+ //
+
+ m_configuration = m_profile.getConfiguration();
+ m_parameters = m_profile.getParameters();
+
+ //
// Get the set of parts within this part and add them as nested
models
// within this model. There is an issue here concerning selection of
// our classloader as the parent for the parts - in effect we need a
@@ -250,6 +266,40 @@
}
}

+ public void setConfiguration( Configuration configuration )
+ {
+ m_configuration = configuration;
+ }
+
+ public Configuration getConfiguration()
+ {
+ if( null == m_configuration )
+ {
+ return new DefaultConfiguration( "configuration", null );
+ }
+ else
+ {
+ return m_configuration;
+ }
+ }
+
+ public void setParameters( Parameters parameters )
+ {
+ m_parameters = parameters;
+ }
+
+ public Parameters getParameters()
+ {
+ if( null == m_parameters )
+ {
+ return DefaultParameters.EMPTY_PARAMETERS;
+ }
+ else
+ {
+ return m_parameters;
+ }
+ }
+
public Object getValue()
{
try

Modified:
development/main/metro/composition/testing/activity/src/main/net/dpml/test/Component.java
==============================================================================
---
development/main/metro/composition/testing/activity/src/main/net/dpml/test/Component.java
(original)
+++
development/main/metro/composition/testing/activity/src/main/net/dpml/test/Component.java
Mon Apr 18 11:16:57 2005
@@ -18,6 +18,8 @@

package net.dpml.test;

+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.logging.Logger;

import javax.swing.event.ChangeEvent;
@@ -25,12 +27,13 @@

import net.dpml.state.State;
import net.dpml.state.StateManager;
+import net.dpml.state.impl.DefaultState;
+import net.dpml.state.impl.AbstractStateManager;

/**
- * Experimental component dealing with activitiy state management.
+ * Experimental component dealing with state management.
*
- * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta
- * Library</a>
+ * @author <a href="mailto:info AT dpml.net";>The Digital Product Meta
Library</a>
*/
public class Component
{
@@ -44,7 +47,7 @@
// state
// ------------------------------------------------------------------

- private final Logger m_logger;
+ private final Logger m_logger;

// ------------------------------------------------------------------
// constructor
@@ -53,209 +56,106 @@
/**
* Creation of a component that describes an activity model.
*
- * @param logger
- * the logging channel assigned by the container
+ * @param logger the logging channel assigned by the container
*/
public Component( final Logger logger )
- {
- m_logger = logger;
- }
-
- private Logger getLogger()
{
- return m_logger;
+ m_logger = logger;
}

// ------------------------------------------------------------------
- // Activity
+ // implementation
// ------------------------------------------------------------------

- public static class Controller implements StateManager
+ public class Controller extends AbstractStateManager
{
- public static final AbstractState AVAILABLE = new Available();
-
- public static final AbstractState STARTED = new Started();
-
- public static final AbstractState STOPPED = new Stopped();
-
- public static final AbstractState TERMINATED = new Terminated();
-
- protected final Component m_instance;
-
- protected final ChangeListener m_listener;
-
- protected final Logger m_logger;
-
- protected State m_state = AVAILABLE;
-
- public Controller( Component instance, Logger logger,
- ChangeListener listener )
- {
- m_instance = instance;
- m_listener = listener;
- m_logger = logger;
- }
-
- public synchronized State getState()
- {
- return m_state;
- }
-
- public synchronized void initialize()
- {
- m_logger.info( "initializing" );
- if ( null!=m_state && m_state == AVAILABLE )
- {
- setState(AVAILABLE);
- }
- else
- {
- final String error = "Cannot initialize from state: " +
m_state;
- throw new IllegalStateException( error );
- }
- m_logger.info( "initialized" );
- }
-
-
-
- public synchronized void terminate()
- {
- m_logger.info( "terminating" );
- if ( m_state == STARTED )
- {
- m_instance.stop();
- }
- setState( TERMINATED );
- m_logger.info( "terminated" );
- }
-
- private synchronized void setState( State state )
+ public Controller( Logger logger, ChangeListener listener, Component
instance )
{
- if ( m_state != state )
- {
- ( (AbstractState) m_state ).execute(m_instance);
- m_state = state;
- ChangeEvent event = new ChangeEvent( this );
- m_listener.stateChanged( event );
- }
+ super( logger, listener, instance );
}

- public State apply( String iTransition ) throws Exception
+ protected DefaultState getStateModel()
{
- // No transitions routed (yet), so nothing to do
- // TODO: move the state logic from initialize into here
- State next = null;
- if ( iTransition.equals( STARTED.getName() ) )
- {
- next = STARTED;
- }
- if ( iTransition.equals( AVAILABLE.getName() ) )
- {
- next = AVAILABLE;
- }
- if ( iTransition.equals( STOPPED.getName() ) )
- {
- next = STOPPED;
- }
- if ( iTransition.equals( TERMINATED.getName() ) )
- {
- next = TERMINATED;
- }
- if ( null != next )
- {
- setState( next );
- }
- return m_state;
+ return STATE;
}
}

- // ------------------------------------------------------------------
- // state transitions
- // ------------------------------------------------------------------
-
- private void start()
- {
- getLogger().info( "started" );
- }
-
- private void stop()
+ private Logger getLogger()
{
- getLogger().info( "stopped" );
+ return m_logger;
}

- // ------------------------------------------------------------------
- // states
- // ------------------------------------------------------------------
+ //
+ // The following methods are invoked by the state manager based on
+ // uris assigned to transitions. The state manager locates the method
+ // from the uri and invokes the operation after constructing arguments
+ // from the possible set of Logger, Object (component impl), State
(initial),
+ // and State (target). If the method requires the target state it must
+ // delcare both current and target state in the method parameters.
+ //

- public static abstract class AbstractState implements State
+ public void terminate( State state )
{
-
- public String getName()
- {
- return getClass().getName();
- }
-
- public abstract void execute( Component activity );
-
+ String name = state.getName();
+ getLogger().info( "handling termination within " + name );
}

- public static class Available extends AbstractState
+ public void start( State starting )
{
-
- public void execute( Component iActivity )
- {
- }
-
- public String[] getTransitions()
- {
- String[] transitions = {Controller.STARTED.getName()};
- return transitions;
- }
+ String from = starting.getName();
+ getLogger().info( "starting from within " + from );
}

- public static class Started extends AbstractState
+ public void stop()
{
- public String[] getTransitions()
- {
- String[] transitions = {Controller.STOPPED.getName()};
- return transitions;
- }
+ getLogger().info( "stopping the component" );
+ }

- public void execute( Component iActivity )
- {
+ // ------------------------------------------------------------------
+ // static utilities
+ // ------------------------------------------------------------------

- iActivity.start();
- }
- }
+ private static final DefaultState STATE = setupStateGraph();

- public static class Stopped extends AbstractState
+ private static DefaultState setupStateGraph()
{
+ //
+ // construct a state graph
+ //

- public void execute( Component iActivity )
- {
-
- iActivity.stop();
+ DefaultState root = new DefaultState();
+ DefaultState initialized = root.addInitialState( "initialized" );
+ DefaultState available = root.addState( "available" );
+ DefaultState started = available.addState( "started" );
+ DefaultState stopped = available.addState( "stopped" );
+ DefaultState terminated = root.addTerminalState( "terminated" );

- }
+ //
+ // create the operations and add then under transitions
+ // (currently declaring method based execution because this is the
+ // only strategy implementated but the idea is to support registered
+ // handlers and/or delegation to identified parts)
+ //

- public String[] getTransitions()
+ try
{
- String[] transitions = {Controller.TERMINATED.getName()};
- return transitions;
- }
- }
-
- public static class Terminated extends AbstractState
- {
+ URI terminate = new URI( "method:terminate" );
+ URI start = new URI( "method:start" );
+ URI stop = new URI( "method:stop" );

- public void execute( Component iActivity )
- {
+ root.addTerminatingTransition( "terminate", terminate,
terminated );
+ initialized.addTransition( "start", start, started );
+ started.addTransition( "stop", stop, stopped );
+ started.addTerminatingTransition( "terminate", stop, stopped );
+ stopped.addTransition( "start", start, started );
}
-
- public String[] getTransitions()
+ catch( URISyntaxException e )
{
- // this is the end
+ // will not happen
+ e.printStackTrace();
return null;
}
+
+ return root;
}
}

Modified:
development/main/metro/composition/testing/activity/src/main/net/dpml/test/Container.java
==============================================================================
---
development/main/metro/composition/testing/activity/src/main/net/dpml/test/Container.java
(original)
+++
development/main/metro/composition/testing/activity/src/main/net/dpml/test/Container.java
Mon Apr 18 11:16:57 2005
@@ -69,16 +69,8 @@
{
Parts parts = getParts();
StateManager manager = parts.selectTestStateManager();
- State initial = manager.getState();
- getLogger().info( "initial state is : " + initial.getName() );
manager.initialize();
- State initialized = manager.getState();
- getLogger().info( "initialized state is : " + initialized.getName()
);
- String[] transitions = initialized.getTransitions();
- for( int i=0; i<transitions.length; i++ )
- {
- getLogger().info( "transition : " + transitions[i] );
- }
+ manager.apply( "start" );
manager.terminate();
}


Modified:
development/main/metro/composition/testing/activity/src/test/net/dpml/test/ComponentTestCase.java
==============================================================================
---
development/main/metro/composition/testing/activity/src/test/net/dpml/test/ComponentTestCase.java
(original)
+++
development/main/metro/composition/testing/activity/src/test/net/dpml/test/ComponentTestCase.java
Mon Apr 18 11:16:57 2005
@@ -20,13 +20,17 @@
package net.dpml.test;

import java.net.URI;
+import java.util.List;
+import java.util.LinkedList;

import junit.framework.TestCase;
+
import net.dpml.automation.model.Model;
import net.dpml.composition.models.Holder;
import net.dpml.composition.unit.CompositionHelper;
import net.dpml.state.State;
import net.dpml.state.StateManager;
+import net.dpml.state.Transition;

/**
* Test a simple component case.
@@ -47,22 +51,21 @@
Holder holder = getComponentHolder();
StateManager manager = (StateManager) holder.getProcessController();
manager.initialize();
- boolean finished = false;
- while ( !finished )
+ List list = new LinkedList();
+ while ( false == list.contains( manager.getState() ) )
{
-
- State currentState = manager.getState();
- String[] transitions = currentState.getTransitions();
- System.out.println( "current state: " + currentState.getName() );
- if ( transitions != null && transitions.length > 0 )
- {
- // just always take the first transition
- System.out.println( "# transition: " + transitions[0] );
- currentState = manager.apply( transitions[0] );
- }
- else
+ State state = manager.getState();
+ list.add( state );
+ String[] transitions = state.getTransitionNames();
+ for( int i=0; i<transitions.length; i++ )
{
- finished = true;
+ String name = transitions[i];
+ Transition transition = state.getTransition( name );
+ if( false == transition.isTerminating() )
+ {
+ manager.apply( name );
+ break;
+ }
}
}
manager.terminate();

Modified: development/main/metro/index.xml
==============================================================================
--- development/main/metro/index.xml (original)
+++ development/main/metro/index.xml Mon Apr 18 11:16:57 2005
@@ -47,6 +47,10 @@
<type>jar</type>
</types>
</info>
+ <dependencies>
+ <include key="dpml-state-api"/>
+ <include key="dpml-transit-main"/>
+ </dependencies>
</project>

<!-- Automation -->
@@ -472,6 +476,8 @@
<include key="dpml-transit-main" tag="api"/>
<include key="dpml-state-api" tag="api"/>
<include key="dpml-activity-api" tag="api"/>
+ <include key="dpml-parameters-api" tag="api"/>
+ <include key="dpml-configuration-api" tag="api"/>
<include key="dpml-automation-control" tag="spi"/>
<include key="dpml-composition-api" tag="spi"/>
<include key="dpml-system-api" tag="spi"/>
@@ -479,6 +485,7 @@
<include key="dpml-util-exception"/>
<include key="commons-cli"/>
<include key="dpml-parameters-impl"/>
+ <include key="dpml-configuration-impl"/>
</dependencies>
</project>

@@ -555,6 +562,7 @@
</info>
<dependencies>
<include key="dpml-state-api"/>
+ <include key="dpml-state-impl"/>
<include key="dpml-activity-api"/>
<include key="dpml-composition-unit" runtime="false"/>
</dependencies>

Added:
development/main/metro/state/api/src/main/net/dpml/state/DuplicateKeyException.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/api/src/main/net/dpml/state/DuplicateKeyException.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state;
+
+
+/**
+ * Exception thrown when a operation attempts to overrite an existing key.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public class DuplicateKeyException extends RuntimeException
+{
+ private String m_transition;
+
+ /**
+ * Construct a new <code>DuplicateKeyException</code> instance.
+ *
+ * @param transition the transition name
+ */
+ public DuplicateKeyException( final String transition )
+ {
+ super( transition );
+ }
+
+}
+

Added:
development/main/metro/state/api/src/main/net/dpml/state/NoSuchStateException.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/api/src/main/net/dpml/state/NoSuchStateException.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state;
+
+
+/**
+ * Exception thrown when a request is made to apply a transition that is
unknown
+ * relative to the target state.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public class NoSuchStateException extends RuntimeException
+{
+ private String m_transition;
+
+ /**
+ * Construct a new <code>NoSuchStateException</code> instance.
+ *
+ * @param transition the transition name
+ */
+ public NoSuchStateException( final String transition )
+ {
+ super( transition );
+ }
+
+}
+

Modified: development/main/metro/state/api/src/main/net/dpml/state/State.java
==============================================================================
--- development/main/metro/state/api/src/main/net/dpml/state/State.java
(original)
+++ development/main/metro/state/api/src/main/net/dpml/state/State.java Mon
Apr 18 11:16:57 2005
@@ -33,8 +33,28 @@
String getName();

/**
- * Return the array of transition operations available under the state.
+ * Return the array of available transition names.
+ *
* @return the transition names
*/
- String[] getTransitions();
+ String[] getTransitionNames();
+
+ /**
+ * Return a named transition.
+ * @return the transition
+ */
+ Transition getTransition( String key );
+
+ /**
+ * Return true if this is a terminal state.
+ * @return TRUE if terminal
+ */
+ boolean isTerminal();
+
+ /**
+ * Return a string representation of the state graph.
+ * @return the string representation
+ */
+ String list();
+
}

Modified:
development/main/metro/state/api/src/main/net/dpml/state/StateManager.java
==============================================================================
---
development/main/metro/state/api/src/main/net/dpml/state/StateManager.java
(original)
+++
development/main/metro/state/api/src/main/net/dpml/state/StateManager.java
Mon Apr 18 11:16:57 2005
@@ -26,42 +26,27 @@
public interface StateManager
{
/**
- * Initialize the component. If the component declares an inner
Controller
- * class and if the class implements an "initialize" operation then
execution
- * of the operation will be delegated to that method otherwise the method
- * does nothing and returns.
+ * Initialize the component.
*/
void initialize() throws Exception;

/**
- * If the component declares an inner Controller class and the class
- * implements the getState method with Object return type, the current
- * state will be derived from the short classname of the returned instance
- * otherwise an empty state (i.e. a state with not transitions) will be
- * returned.
+ * Return the current state of the component.
+ * @return the current state
+ * @exception IllegalStateException if the manager has not been
initialized
*/
- State getState();
+ State getState() throws IllegalStateException;

/**
- * Termination of the component. If the component declares an inner
Controller
- * class and if the class implements an "terminate" operation then
execution
- * of the operation will be delegated to that method otherwise the method
- * does nothing and returns.
+ * Termination of the component.
*/
void terminate();

/**
- * Applies a state transition to the controller. If the transition
argument
- * corresponds to a method name declared by the current state object
returned
- * from $Controller.getState(), that method will be invoked. The object
retured
- * from the operation will be evaluated as a state object thereby
establishing
- * the available transitions. If the transition argument does not match
a method
- * declared by the current state object exposed by the controller an
- * IllegalArgumentException will be thrown.
+ * Applies a state transition identified by a supplied transition key.
*
- * @param transition the name of a transition to apply to the component's
controller
- * @return the state established by the transition
+ * @param key the key identifying the transition to apply to the
component's controller
* @exception if a transition error occurs
*/
- State apply( String transition ) throws Exception;
+ void apply( String key ) throws Exception;
}

Added:
development/main/metro/state/api/src/main/net/dpml/state/Transition.java
==============================================================================
--- (empty file)
+++ development/main/metro/state/api/src/main/net/dpml/state/Transition.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state;
+
+/**
+ * The State interface is an interface representing an immutable state of
+ * a component instance.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public interface Transition
+{
+ /**
+ * Return the target of this transition.
+ * @return the transition target
+ */
+ State getTargetState();
+
+ /**
+ * Return true if this transition can participate in a termination.
+ * @return the transition names
+ */
+ boolean isTerminating();
+
+}

Added: development/main/metro/state/build.xml
==============================================================================
--- (empty file)
+++ development/main/metro/state/build.xml Mon Apr 18 11:16:57 2005
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<project name="dpml-state" default="default" basedir="."
+ xmlns:transit="antlib:net.dpml.transit">
+
+ <transit:import uri="artifact:template:dpml/magic/reactor"/>
+
+</project>

Added:
development/main/metro/state/impl/src/main/net/dpml/state/impl/AbstractStateManager.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/impl/src/main/net/dpml/state/impl/AbstractStateManager.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state.impl;
+
+import java.util.Map;
+import java.util.Hashtable;
+import java.util.TreeMap;
+import java.util.ArrayList;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.lang.reflect.Method;
+import java.net.URI;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.dpml.state.State;
+import net.dpml.state.Transition;
+import net.dpml.state.StateManager;
+import net.dpml.state.DuplicateKeyException;
+import net.dpml.state.NoSuchTransitionException;
+
+/**
+ * The State interface is an interface representing an immutable state of
+ * a component instance.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public abstract class AbstractStateManager implements StateManager
+{
+ private DefaultState m_state;
+ private Object m_instance;
+ private ChangeListener m_listener;
+ private Map m_handlers = new Hashtable();
+ private Logger m_logger;
+
+ /**
+ * Creation of a new AbstractStateManager.
+ * @param logger the assinged logging channel
+ * @param listener a change listener
+ * @param instance the managed instance
+ */
+ public AbstractStateManager( Logger logger, ChangeListener listener,
Object instance )
+ {
+ m_instance = instance;
+ m_listener = listener;
+ m_logger = logger;
+ }
+
+ /**
+ * Operation to be overriden by derived types. The operation returns the
root state
+ * representing the complete state graph for the component.
+ * @return the root state
+ */
+ protected abstract DefaultState getStateModel();
+
+ /**
+ * Return an registered transition handler.
+ * @param uri a handler uri
+ * @return the handler instance
+ */
+ protected Object getHandler( String key ) throws NoSuchHandlerException
+ {
+ Object handler = m_handlers.get( key );
+ if( null == handler )
+ {
+ throw new NoSuchHandlerException( key );
+ }
+ return handler;
+ }
+
+ protected void addHandler( String key, Object handler )
+ {
+ if( m_handlers.containsKey( key ) )
+ {
+ throw new DuplicateKeyException( key );
+ }
+ else
+ {
+ Class clazz = handler.getClass();
+ locateMethod( clazz, "handle" );
+ m_handlers.put( key, handler );
+ }
+ }
+
+ /**
+ * Initialization of the manager by a controller. This implementation
will attempt assign
+ * an initial state declared within the supplied state graph. If no
initial state is present
+ * the implementation will return the root state.
+ */
+ public synchronized void initialize() throws Exception
+ {
+ try
+ {
+ m_state = getStateModel().getInitialState();
+ }
+ catch( Throwable e )
+ {
+ m_state = getStateModel();
+ }
+ }
+
+ /**
+ * Retun the current state of execution.
+ *
+ * @return the state representing the current state of execution
+ */
+ public synchronized State getState()
+ {
+ return getCurrentState().getProxy();
+ }
+
+ /**
+ * Utility method to return the current state implemetation class.
+ *
+ * @return the state representing the current state of execution
+ */
+ protected DefaultState getCurrentState()
+ {
+ if( null == m_state )
+ {
+ final String error =
+ "State manager has not been initialized.";
+ throw new IllegalStateException( error );
+ }
+ else
+ {
+ return m_state;
+ }
+ }
+
+ /**
+ * Apply a transition identified by a supplied transition key. The
+ * implementation will attempt to locate the transition relative to the
current
+ * state (taking into account the DefaultState search semantics) fro which
+ * a invocation handler is resolve and invoked. If the transition is
successful
+ * the target tranaistion state declared by the transtion will be
assigned as the
+ * current current state and all listeners will be notified of a state
change.
+ *
+ * @param key the transition key
+ * @exception if a error occurs in transition execution
+ */
+ public void apply( String key ) throws Exception
+ {
+ DefaultState state = getCurrentState();
+ DefaultTransition transition = state.getNamedTransition( key );
+ Object handler = transition.getHandler();
+ DefaultState target = transition.getTransitionTarget();
+ if( getLogger().isLoggable( Level.FINE ) )
+ {
+ final String message =
+ "applying "
+ + handler.toString()
+ + "' to '"
+ + state.getName()
+ + "'";
+ getLogger().fine( message );
+ }
+ Logger logger = getLogger();
+ execute( handler, state, target, logger );
+ setState( target );
+ }
+
+ private void execute( Object handler, State state, State target, Logger
logger ) throws Exception
+ {
+ if( handler instanceof URI )
+ {
+ URI uri = (URI) handler;
+ String scheme = uri.getScheme();
+ if( "part".equals( scheme ) )
+ {
+ //
+ // delegate transition execution to an identified part
+ //
+
+ throw new UnsupportedOperationException( uri.toString() );
+ }
+ else if( "handler".equals( uri ) )
+ {
+ //
+ // delegate execution to an registered handler
+ //
+
+ throw new UnsupportedOperationException( uri.toString() );
+ }
+ else if( "method".equals( scheme ) )
+ {
+ //
+ // invoke the named method on the component instance
+ //
+
+ Class c = m_instance.getClass();
+ String spec = uri.getSchemeSpecificPart();
+ Method method = locateMethod( c, spec );
+ Class[] parameters = method.getParameterTypes();
+ Object[] args = resolveArguments( parameters, state,
target, logger );
+ method.invoke( m_instance, args );
+ }
+ else
+ {
+ final String error =
+ "Scheme not recognized."
+ + "\nScheme: " + scheme
+ + "\nURI: " + uri;
+ throw new IllegalArgumentException( error );
+ }
+ }
+ }
+
+ private Object[] resolveArguments( Class[] parameters, State state,
State target, Logger logger )
+ {
+ boolean firstStateAssigned = false;
+ Object[] args = new Object[ parameters.length ];
+ for( int i=0; i<parameters.length; i++ )
+ {
+ Class c = parameters[i];
+ if( Logger.class.isAssignableFrom( c ) )
+ {
+ args[i] = logger;
+ }
+ else if( State.class.isAssignableFrom( c ) && ( false ==
firstStateAssigned ) )
+ {
+ args[i] = state;
+ firstStateAssigned = true;
+ }
+ else if( State.class.isAssignableFrom( c ) && ( true ==
firstStateAssigned ) )
+ {
+ args[i] = target;
+ }
+ else if( m_instance.getClass().isAssignableFrom( c ) )
+ {
+ args[i] = m_instance;
+ }
+ else
+ {
+ final String error =
+ "Could not resolve a argument value for the parameter type
["
+ + c.getName()
+ + "].";
+ throw new RuntimeException( error );
+ }
+ }
+ return args;
+ }
+
+ /**
+ * Utility operation to apply a state change. If the supplied state is
+ * differnet from the current state the current state will be updated to
+ * the supplied value and a notification event issued to the listener
+ * assigned under the constructor.
+ *
+ * @param state the state to assign as the current state
+ */
+ protected synchronized void setState( DefaultState state )
+ {
+ if( false == m_state.equals( state ) )
+ {
+ if( getLogger().isLoggable( Level.FINE ) )
+ {
+ final String message =
+ "changing state from '"
+ + m_state.getName()
+ + "' to '"
+ + state.getName()
+ + "'";
+ getLogger().fine( message );
+ }
+ m_state = state;
+ ChangeEvent event = new ChangeEvent( this );
+ m_listener.stateChanged( event );
+ }
+ }
+
+ /**
+ * Handles a request for termination by a controller. The implementation
+ * will attempt to locate a transition named 'terminate' and if located,
+ * will apply that transition. If the result of the transition if a new
+ * non-terminal state the procedure will be repeated. If the current
state
+ * is a terminal state the operation simply returns.
+ */
+ public synchronized void terminate()
+ {
+ DefaultState state = getCurrentState();
+ if( state.isTerminal() )
+ {
+ return;
+ }
+ try
+ {
+ apply( "terminate" );
+ if( false == getCurrentState().equals( state ) )
+ {
+ terminate();
+ }
+ }
+ catch( NoSuchTransitionException e )
+ {
+ // no default termination transition registered
+ }
+ catch( Exception e )
+ {
+ getLogger().log( Level.WARNING, "Ignoring termination transtion
error", e );
+ }
+ }
+
+ /**
+ * Utility operation to return the logging channel assigned by the
container.
+ * @return the logging channel
+ */
+ protected Logger getLogger()
+ {
+ return m_logger;
+ }
+
+ private Method locateMethod( Class c, String operation )
+ {
+ Method[] methods = c.getMethods();
+ for( int i=0; i<methods.length; i++ )
+ {
+ Method method = methods[i];
+ String name = method.getName();
+ if( operation.equals( method.getName() ) )
+ {
+ return method;
+ }
+ }
+ final String error =
+ "Class does not implement a public method '"
+ + operation
+ + "'."
+ + "\nClass: " + c.getName();
+ throw new IllegalArgumentException( error );
+ }
+}

Added:
development/main/metro/state/impl/src/main/net/dpml/state/impl/DefaultState.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/impl/src/main/net/dpml/state/impl/DefaultState.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,593 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state.impl;
+
+import java.util.Map;
+import java.util.Hashtable;
+import java.util.TreeMap;
+import java.util.ArrayList;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+
+import net.dpml.state.State;
+import net.dpml.state.NoSuchStateException;
+import net.dpml.state.NoSuchTransitionException;
+import net.dpml.state.DuplicateKeyException;
+import net.dpml.state.Transition;
+
+import net.dpml.lang.DefaultInvocationHandler;
+
+/**
+ * The State interface is an interface representing an immutable state of
+ * a component instance.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public class DefaultState implements State
+{
+ private final String m_name;
+ private final DefaultState m_parent;
+ private final boolean m_terminal;
+
+ private DefaultState m_initial;
+ private final Map m_transitions = new Hashtable();
+ private final Map m_states = new Hashtable();
+ private final State m_proxy;
+
+ /**
+ * Creation of a new top-level non-terminal state.
+ */
+ public DefaultState()
+ {
+ this( false );
+ }
+
+ /**
+ * Creation of a new top-level state.
+ * @param terminal the terminal status of the state
+ */
+ public DefaultState( boolean terminal )
+ {
+ this( null, "root", terminal );
+ if( terminal )
+ {
+ m_initial = this;
+ }
+ }
+
+ /**
+ * Internal constructor for a state instance.
+ *
+ * @paren parent the parent of the created state
+ * @param name the name that the state is index under
+ * @param terminal TRUE if this is a terminal state else FALSE
+ */
+ private DefaultState( DefaultState parent, String name, boolean terminal
)
+ {
+ m_name = name;
+ m_parent = parent;
+ m_terminal = terminal;
+
+ InvocationHandler handler = new DefaultInvocationHandler( this );
+ ClassLoader classloader = getClass().getClassLoader();
+ m_proxy = (State) Proxy.newProxyInstance( classloader, new Class[]{
State.class }, handler );
+
+ }
+
+ /**
+ * Return the name of the state.
+ *
+ * @return the state name
+ */
+ public String getName()
+ {
+ return m_name;
+ }
+
+ /**
+ * Return the parent state.
+ * @return the parent
+ */
+ protected DefaultState getParent()
+ {
+ return m_parent;
+ }
+
+ /**
+ * Return the parent state.
+ * @return the parent
+ */
+ public State getProxy()
+ {
+ return m_proxy;
+ }
+
+ /**
+ * Return TRUE if this state is the initial state. A state graph may have
+ * only one initial state. During ionitialization of a StateManger, the
+ * AbstractStatemanager implementation will attempt to locate the initial
+ * state and assign it as the current state.
+ *
+ * @return true if this state is the initial state
+ */
+ protected boolean isInitial()
+ {
+ return this.equals( getInitialState() );
+ }
+
+ /**
+ * Return the terminal status of this state. If a state is termin if
cannot
+ * contain sub-states or transitions.
+ *
+ * @return the terminal status of this state
+ */
+ public boolean isTerminal()
+ {
+ return m_terminal;
+ }
+
+ /**
+ * Return the inital state is the state graph.
+ *
+ * @return the initial state
+ * @exception IllegalStateException if the inital state has not been
declared
+ */
+ public DefaultState getInitialState()
+ {
+ if( null != m_parent )
+ {
+ return m_parent.getInitialState();
+ }
+ else if( null == m_initial )
+ {
+ final String error =
+ "Initial state has not been declared.";
+ throw new IllegalStateException( error );
+ }
+ return m_initial;
+ }
+
+ /**
+ * Returns an array of transtion names declared in this state and all
+ * parent states.
+ *
+ * @return the available transition names
+ */
+ public String[] getTransitionNames()
+ {
+ if( m_terminal )
+ {
+ return new String[0];
+ }
+ if( null == m_parent )
+ {
+ return getLocalTransitionNames();
+ }
+ else
+ {
+ String[] keys = m_parent.getTransitionNames();
+ String[] local = getLocalTransitionNames();
+ String[] result = new String[ keys.length + local.length ];
+ System.arraycopy( keys, 0, result, 0, keys.length );
+ System.arraycopy( local, 0, result, keys.length, local.length
);
+ return result;
+ }
+ }
+
+ /**
+ * Add a new sub-state to the set of states contained within this
+ * state. If the supplied key is not unique within the scope of the graph
+ * an exception will be thrown.
+ *
+ * @param key the key identifying the new state
+ * @return the created state
+ */
+ public DefaultState addState( String key )
+ {
+ return addState( this, key, false, false );
+ }
+
+ /**
+ * Add a new initial sub-state to the set of states contained within this
+ * state. If the supplied key is not unique within the scope of the graph
+ * an exception will be thrown. If the graph's initial state has already
been
+ * delcared an exception will be thrown.
+ *
+ * @param key the key identifying the new state
+ * @return the created state
+ */
+ public DefaultState addInitialState( String key )
+ {
+ return addState( this, key, true, false );
+ }
+
+ /**
+ * Add a new initial terminal sub-state to the set of states contained
within this
+ * state. If the supplied key is not unique within the scope of the graph
+ * an exception will be thrown. If the graph's initial state has already
been
+ * delcared an exception will be thrown.
+ *
+ * @param key the key identifying the new state
+ * @return the created state
+ */
+ public DefaultState addInitialTerminalState( String key )
+ {
+ return addState( this, key, true, true );
+ }
+
+ /**
+ * Add a new terminal sub-state to the set of states contained within
this
+ * state. If the supplied key is not unique within the scope of the graph
+ * an exception will be thrown.
+ *
+ * @param key the key identifying the new state
+ * @return the created state
+ */
+ public DefaultState addTerminalState( String key )
+ {
+ return addState( this, key, false, true );
+ }
+
+ /**
+ * Internal operation to construct a new sub-state state. The
implementation
+ * will delegate the state addition to the parent under which all states
in the
+ * state graph are aggregated relative to unique keys.
+ *
+ * @param parent the enclosing state
+ * @param key the key identifying the new state
+ * @param initial true if this state is to be assinged as the default
initial state
+ * @param terminal true if this is a terminal state
+ * @return the created state
+ * @exception IllegalStateException if the parent state is a terminal
state
+ * @exception DuplicateKeyException if a state with the supplied key is
already assingned
+ * @exception IllegalArgumentException if the initial parameter is true
and an initial
+ * state has already beeen declared
+ */
+ private DefaultState addState( DefaultState parent, String key, boolean
initial, boolean terminal )
+ {
+ if( m_parent != null )
+ {
+ return m_parent.addState( parent, key, initial, terminal );
+ }
+ else
+ {
+ if( parent.isTerminal() )
+ {
+ final String error =
+ "Cannot add substate to a terminal state."
+ + "\nState: " + parent.getName()
+ + "\nSubstate: " + key;
+ throw new IllegalStateException( error );
+ }
+
+ if( m_states.containsKey( key ) )
+ {
+ throw new DuplicateKeyException( key );
+ }
+ else
+ {
+ if( initial )
+ {
+ if( m_initial != null )
+ {
+ final String error =
+ "Initial state already assigned."
+ + "\nInitial: " + m_initial.getName();
+ throw new IllegalArgumentException( error );
+ }
+ }
+ DefaultState state = new DefaultState( parent, key, terminal
);
+ m_states.put( key, state );
+ if( initial )
+ {
+ m_initial = state;
+ }
+ return state;
+ }
+ }
+ }
+
+ /**
+ * Return an array of all states within the state graph. This includes
+ * all substates of the root state and all substates of substates, etc.
+ * It does not include the root state itself.
+ *
+ * @return the state array
+ */
+ protected DefaultState[] getAllStates()
+ {
+ if( null != m_parent )
+ {
+ return m_parent.getAllStates();
+ }
+ else
+ {
+ ArrayList list = new ArrayList();
+ return (DefaultState[]) m_states.values().toArray( new
DefaultState[0] );
+ }
+ }
+
+ /**
+ * Return an array of local states contained within this state.
+ * @return the local state array
+ */
+ protected DefaultState[] getStates()
+ {
+ return getStates( this );
+ }
+
+ /**
+ * Return an array of all states contained within the supplied state.
+ * @param parent the enclosing parent state
+ * @return the state array
+ */
+ protected DefaultState[] getStates( DefaultState parent )
+ {
+ ArrayList list = new ArrayList();
+ DefaultState[] states = getAllStates();
+ for( int i=(states.length -1); i>-1; i-- )
+ {
+ DefaultState state = states[i];
+ if( parent.equals( state.getParent() ) )
+ {
+ list.add( state );
+ }
+ }
+ return (DefaultState[]) list.toArray( new DefaultState[0] );
+ }
+
+ /**
+ * Return a state from the state graph mathcing the supplied key.
+ * @param key the state key
+ * @return the state assigned to the key
+ * @exception NoSuchStateException if the key does not match a state
+ * within the state graph
+ */
+ protected DefaultState getState( String key )
+ {
+ if( null != m_parent )
+ {
+ return m_parent.getState( key );
+ }
+ else
+ {
+ DefaultState state = (DefaultState) m_states.get( key );
+ if( null == state )
+ {
+ throw new NoSuchStateException( key );
+ }
+ else
+ {
+ return state;
+ }
+ }
+ }
+
+ /**
+ * Add a transition to the state. Transition keys are unique within the
scope
+ * of the state under which the transition is assigned. Transitions with
the
+ * same name as a transition within a parent state will take precedence
when
+ * selcting transitions.
+ *
+ * @param key the transition key
+ * @param handler the object to be assinged as the handler of the
transition action
+ * @param target the transition target state
+ */
+ public void addTransition( String key, Object handler, DefaultState
target )
+ {
+ addTransition( key, handler, target, false );
+ }
+
+ /**
+ * Add a terminating transition to the state. Transition keys are unique
within the scope
+ * of the state under which the transition is assigned. Transitions with
the
+ * same name as a transition within a parent state will take precedence
when
+ * selcting transitions. Terminating transitions are a class of
transition that
+ * contribute to the termination of a system and may be applied
automatically by the
+ * AbstractStateManager following a termination request by a controlling
system.
+ *
+ * @param key the transition key
+ * @param handler the object to be assinged as the handler of the
transition action
+ * @param target the transition target state
+ */
+ public void addTerminatingTransition( String key, Object handler,
DefaultState target )
+ {
+ addTransition( key, handler, target, true );
+ }
+
+ /**
+ * Internal operation to add a new transtion to this state.
+ * @param key the transition key
+ * @param handler the transition handler
+ * @param target the transition target
+ * @param terminating TRUE if this is a terminating transition
+ */
+ private void addTransition( String key, Object handler, DefaultState
target, boolean terminating )
+ {
+ if( isTerminal() )
+ {
+ final String error =
+ "Cannot add a transtion to a terminal state.";
+ throw new IllegalStateException( error );
+ }
+ if( m_transitions.containsKey( key ) )
+ {
+ throw new DuplicateKeyException( key );
+ }
+ DefaultTransition transition = new DefaultTransition( handler,
target, terminating );
+ m_transitions.put( key, transition );
+ }
+
+ /**
+ * Return the fisrt transition matching the supplied key in this state
graph. The search
+ * for a matching transition proceeds from the local state upwards uptil
the root state is
+ * reached unless a local or intermidiate transition matches the supplied
key.
+ *
+ * @param key the transition key
+ * @return a matching transition
+ * @exception NoSuchTransitionException if no matching transition can be
found
+ */
+ public Transition getTransition( String key )
+ {
+ return getNamedTransition( key );
+ }
+
+ /**
+ * Return the fisrt transition matching the supplied key in this state
graph. The search
+ * for a matching transition proceeds from the local state upwards uptil
the root state is
+ * reached unless a local or intermidiate transition matches the supplied
key.
+ *
+ * @param key the transition key
+ * @return a matching transition
+ * @exception NoSuchTransitionException if no matching transition can be
found
+ */
+ public DefaultTransition getNamedTransition( String key )
+ {
+ DefaultTransition transition = (DefaultTransition)
m_transitions.get( key );
+ if( null != transition )
+ {
+ return transition;
+ }
+ else
+ {
+ if( null == m_parent )
+ {
+ throw new NoSuchTransitionException( key );
+ }
+ else
+ {
+ return m_parent.getNamedTransition( key );
+ }
+ }
+ }
+
+ /**
+ * Return the list of transition names local to this state.
+ * @return the array of transition names
+ */
+ protected String[] getLocalTransitionNames()
+ {
+ return (String[]) m_transitions.keySet().toArray( new String[0] );
+ }
+
+ /**
+ * return a string representation of this state.
+ * @return the string representation
+ */
+ public String toString()
+ {
+ if( null != m_parent )
+ {
+ return "[state " + m_name + " (in:" + m_parent.getName() + ")]";
+ }
+ else
+ {
+ return "[state " + m_name + "]";
+ }
+ }
+
+ public boolean equals( Object other )
+ {
+ if( null == other )
+ {
+ return false;
+ }
+ else if( this.hashCode() == other.hashCode() )
+ {
+ return true;
+ }
+ else if( other instanceof DefaultState )
+ {
+ DefaultState state = (DefaultState) other;
+ return m_name.equals( state.getName() );
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private boolean equals( Object one, Object two )
+ {
+ if( null == one )
+ {
+ return ( null == two );
+ }
+ else
+ {
+ return one.equals( two );
+ }
+ }
+
+ public int hashCode()
+ {
+ return m_name.hashCode();
+ }
+
+
+ /**
+ * Untility function that returns a string representation of this state
+ * in the form of a graph.
+ * @return the string representation of the graph represented by this
+ * node and all child nodes
+ */
+ public String list()
+ {
+ StringBuffer buffer = new StringBuffer();
+ graph( buffer, this, "" );
+ return buffer.toString();
+ }
+
+ /**
+ * Internal utility to handle graph construction.
+ * @param buffer a string buffer to write to
+ * @param state the state to include
+ * @param offset a character offset to apply when writting out the state
features
+ */
+ private void graph( StringBuffer buffer, DefaultState state, String
offset )
+ {
+ String name = state.getName();
+ buffer.append( "\n" + offset );
+ buffer.append( "state: " + name );
+ if( state.isInitial() )
+ {
+ buffer.append( " (initial)" );
+ }
+ if( state.isTerminal() )
+ {
+ buffer.append( " (terminal)" );
+ }
+
+ String[] transitions = state.getLocalTransitionNames();
+ for( int i=0; i<transitions.length; i++ )
+ {
+ String t = transitions[i];
+ DefaultTransition transition = state.getNamedTransition( t );
+ State target = transition.getTargetState();
+ buffer.append( "\n " + offset + "transition:" + t + " -->
state:" + target.getName() );
+ }
+
+ DefaultState[] states = state.getStates();
+ for( int i=0; i<states.length; i++ )
+ {
+ DefaultState s = states[i];
+ graph( buffer, s, offset + " " );
+ }
+ }
+}

Added:
development/main/metro/state/impl/src/main/net/dpml/state/impl/DefaultTransition.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/impl/src/main/net/dpml/state/impl/DefaultTransition.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state.impl;
+
+import java.util.Map;
+import java.util.Hashtable;
+import java.util.TreeMap;
+import java.util.ArrayList;
+
+import net.dpml.state.State;
+import net.dpml.state.Transition;
+
+/**
+ * The State interface is an interface representing an immutable state of
+ * a component instance.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public class DefaultTransition implements Transition
+{
+ private final DefaultState m_target;
+ private final Object m_handler;
+ private final boolean m_terminating;
+
+ protected DefaultTransition( Object handler, DefaultState target,
boolean terminating )
+ {
+ m_handler = handler;
+ m_target = target;
+ m_terminating = terminating;
+ }
+
+ public Object getHandler()
+ {
+ return m_handler;
+ }
+
+ public State getTargetState()
+ {
+ return getTransitionTarget();
+ }
+
+ public DefaultState getTransitionTarget()
+ {
+ return m_target;
+ }
+
+ public boolean isTerminating()
+ {
+ return m_terminating;
+ }
+}

Added:
development/main/metro/state/impl/src/main/net/dpml/state/impl/NoSuchHandlerException.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/impl/src/main/net/dpml/state/impl/NoSuchHandlerException.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state.impl;
+
+
+/**
+ * Exception thrown when a request for a handler could not be reoslved.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ */
+public class NoSuchHandlerException extends RuntimeException
+{
+ private String m_transition;
+
+ /**
+ * Construct a new <code>NoSuchHandlerException</code> instance.
+ *
+ * @param handler the handler name
+ */
+ public NoSuchHandlerException( final String handler )
+ {
+ super( handler );
+ }
+
+}
+

Added:
development/main/metro/state/impl/src/test/net/dpml/state/impl/DefaultStateTestCase.java
==============================================================================
--- (empty file)
+++
development/main/metro/state/impl/src/test/net/dpml/state/impl/DefaultStateTestCase.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.state.impl;
+
+import junit.framework.TestCase;
+
+import net.dpml.state.State;
+
+/**
+ * Test creation of a state model.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ * @version $Id: AbstractDescriptorTestCase.java 1556 2005-01-22 12:43:42Z
niclas $
+ */
+public class DefaultStateTestCase extends TestCase
+{
+ /**
+ * Test the DefaultState as a vehicle for the creation of a state model.
+ */
+ public void testModelCreation() throws Exception
+ {
+ //
+ // construct a state graph
+ //
+
+ DefaultState root = new DefaultState();
+ DefaultState initialized = root.addInitialState( "initialized" );
+ DefaultState available = root.addState( "available" );
+ DefaultState started = available.addState( "started" );
+ DefaultState stopped = available.addState( "stopped" );
+ DefaultState terminated = root.addTerminalState( "terminated" );
+
+ //
+ // create the operations and add then under transitions
+ //
+
+ Operation terminate = new Operation( "terminate" );
+ Operation start = new Operation( "start" );
+ Operation stop = new Operation( "stop" );
+ root.addTerminatingTransition( "terminate", terminate, terminated );
+ initialized.addTransition( "start", start, started );
+ started.addTransition( "stop", stop, stopped );
+ stopped.addTransition( "start", start, started );
+
+ String audit = root.list();
+ System.out.println( audit );
+ }
+
+ private static class Operation
+ {
+ final String m_name;
+ public Operation( String name )
+ {
+ m_name = name;
+ }
+ public void execute()
+ {
+ System.out.println( "executing " + m_name );
+ }
+ }
+}
+
+

Added:
development/main/transit/core/handler/src/main/net/dpml/lang/DefaultInvocationHandler.java
==============================================================================
--- (empty file)
+++
development/main/transit/core/handler/src/main/net/dpml/lang/DefaultInvocationHandler.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2005 Stephen J. McConnell.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.lang;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.lang.reflect.Method;
+
+/**
+ * The ConfigurationInvocationHandler class handles the isolation of a
configuration
+ * implementation behind the Configuration class.
+ *
+ * @author <a href="mailto:dev-dpml AT lists.ibiblio.org";>The Digital Product
Meta Library</a>
+ * @version $Id: ApplianceInvocationHandler.java 2106 2005-03-21 18:46:10Z
mcconnell AT dpml.net $
+ */
+public final class DefaultInvocationHandler
+ implements InvocationHandler
+{
+ //-------------------------------------------------------------------
+ // state
+ //-------------------------------------------------------------------
+
+ private final Object m_instance;
+
+ //-------------------------------------------------------------------
+ // constructor
+ //-------------------------------------------------------------------
+
+ /**
+ * Create a proxy invocation handler.
+ *
+ * @param instance the instance
+ */
+ public DefaultInvocationHandler( Object instance )
+ {
+ assertNotNull( instance, "instance" );
+ m_instance = instance;
+ }
+
+ //-------------------------------------------------------------------
+ // InvocationHandler
+ //-------------------------------------------------------------------
+
+ /**
+ * Invoke the specified method on underlying object.
+ * This is called by the proxy object.
+ *
+ * @param proxy the proxy object
+ * @param method the method invoked on proxy object
+ * @param args the arguments supplied to method
+ * @return the return value of method
+ * @throws Throwable if an error occurs
+ */
+ public Object invoke( final Object proxy, final Method method, final
Object[] args )
+ throws Throwable
+ {
+ try
+ {
+ return method.invoke( m_instance, args );
+ }
+ catch( Throwable e )
+ {
+ throw handleInvocationThrowable( method, e );
+ }
+ }
+
+ private Throwable handleInvocationThrowable( Method method, Throwable e )
+ {
+ final String name = method.getName();
+ final String error =
+ "Delegation error while attempting to invoke the operation ["
+ + name
+ + "].";
+
+ while( true )
+ {
+ if( e instanceof UndeclaredThrowableException )
+ {
+ Throwable cause =
+ ((UndeclaredThrowableException)e).getUndeclaredThrowable();
+ if( cause == null )
+ {
+ return new DelegationRuntimeException( error, e );
+ }
+ else
+ {
+ e = cause;
+ }
+ }
+ else if( e instanceof InvocationTargetException )
+ {
+ Throwable cause =
+ ((InvocationTargetException)e).getTargetException();
+ if( cause == null )
+ {
+ return new DelegationRuntimeException( error, e );
+ }
+ else
+ {
+ e = cause;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ return e;
+ }
+
+
+
+ private void assertNotNull( Object object, String key )
+ throws NullArgumentException
+ {
+ if( null == object )
+ {
+ throw new NullArgumentException( key );
+ }
+ }
+}

Added:
development/main/transit/core/handler/src/main/net/dpml/lang/DelegationRuntimeException.java
==============================================================================
--- (empty file)
+++
development/main/transit/core/handler/src/main/net/dpml/lang/DelegationRuntimeException.java
Mon Apr 18 11:16:57 2005
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2005 Stephen McConnell
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.dpml.lang;
+
+
+/**
+ * Exception thrown in response to a unexpected error in an invocation
handler.
+ *
+ */
+public class DelegationRuntimeException extends RuntimeException
+{
+ /**
+ * Creation of a new delegation exception.
+ * @param message the message
+ * @param cause the causal exception
+ */
+ public DelegationRuntimeException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+}
\ No newline at end of file



  • svn commit: r2304 - in development/main: metro metro/composition/control/src/main/net/dpml/composition/control metro/composition/control/src/main/net/dpml/composition/models metro/composition/testing/activity/src/main/net/dpml/test metro/composition/testing/activity/src/test/net/dpml/test metro/state metro/state/api metro/state/api/src/main/net/dpml/state metro/state/impl metro/state/impl/src/main/net/dpml/state/impl metro/state/impl/src/test metro/state/impl/src/test/net metro/state/impl/src/test/net/dpml metro/state/impl/src/test/net/dpml/state metro/state/impl/src/test/net/dpml/state/impl transit/core/handler/src/main/net/dpml/lang, mcconnell, 04/18/2005

Archive powered by MHonArc 2.6.24.

Top of Page