com.googlecode.stateless4j.StateMachine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stateless4j Show documentation
Show all versions of stateless4j Show documentation
A fluent interface for state machines
The newest version!
package com.googlecode.stateless4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import com.googlecode.stateless4j.delegates.Action1;
import com.googlecode.stateless4j.delegates.Action2;
import com.googlecode.stateless4j.delegates.Func;
import com.googlecode.stateless4j.delegates.Func2;
import com.googlecode.stateless4j.resources.StateMachineResources;
import com.googlecode.stateless4j.transitions.Transition;
import com.googlecode.stateless4j.triggers.TriggerBehaviour;
import com.googlecode.stateless4j.triggers.TriggerWithParameters;
import com.googlecode.stateless4j.triggers.TriggerWithParameters1;
import com.googlecode.stateless4j.triggers.TriggerWithParameters2;
import com.googlecode.stateless4j.triggers.TriggerWithParameters3;
import com.googlecode.stateless4j.validation.Enforce;
///
/// Models behaviour as transitions between a finite set of states.
///
/// The type used to represent the states.
/// The type used to represent the triggers that cause state transitions.
public class StateMachine
{
final Map> _stateConfiguration = new HashMap>();
final Map> _triggerConfiguration = new HashMap>();
final Func _stateAccessor;
final Action1 _stateMutator;
Action2 _unhandledTriggerAction = new Action2() {
public void doIt(TState state, TTrigger trigger) throws Exception {
throw new Exception(
String.format(
StateMachineResources.NoTransitionsPermitted,
trigger, state));
}
};
///
/// Construct a state machine.
///
/// The initial state.
public StateMachine(TState initialState)
{
final StateReference reference = new StateReference();
reference.setState(initialState);
_stateAccessor = new Func() {
public TState call() {
return reference.getState();
}
};
_stateMutator = new Action1() {
public void doIt(TState s) {
reference.setState(s);
};
};
}
///
/// The current state.
///
public TState getState()
{
return _stateAccessor.call();
}
private void setState(TState value)
{
_stateMutator.doIt(value);
}
///
/// The currently-permissible trigger values.
///
public List getPermittedTriggers()
{
return getCurrentRepresentation().getPermittedTriggers();
}
StateRepresentation getCurrentRepresentation()
{
return GetRepresentation(getState());
}
StateRepresentation GetRepresentation(TState state)
{
if (!_stateConfiguration.containsKey(state))
{
StateRepresentation result = new StateRepresentation(state);
_stateConfiguration.put(state, result);
}
return _stateConfiguration.get(state);
}
///
/// Begin configuration of the entry/exit actions and allowed transitions
/// when the state machine is in a particular state.
///
/// The state to configure.
/// A configuration object through which the state can be configured.
public StateConfiguration Configure(TState state) throws Exception
{
return new StateConfiguration(GetRepresentation(state), new Func2>() {
public StateRepresentation call(TState arg0) {
return GetRepresentation(arg0);
}
});
}
///
/// Transition from the current state via the specified trigger.
/// The target state is determined by the configuration of the current state.
/// Actions associated with leaving the current state and entering the new one
/// will be invoked.
///
/// The trigger to fire.
/// The current state does
/// not allow the trigger to be fired.
public void Fire(TTrigger trigger) throws Exception
{
publicFire(trigger, new Object[0]);
}
///
/// Transition from the current state via the specified trigger.
/// The target state is determined by the configuration of the current state.
/// Actions associated with leaving the current state and entering the new one
/// will be invoked.
///
/// Type of the first trigger argument.
/// The trigger to fire.
/// The first argument.
/// The current state does
/// not allow the trigger to be fired.
public void Fire(TriggerWithParameters1 trigger, TArg0 arg0) throws Exception
{
Enforce.ArgumentNotNull(trigger, "trigger");
publicFire(trigger.getTrigger(), arg0);
}
///
/// Transition from the current state via the specified trigger.
/// The target state is determined by the configuration of the current state.
/// Actions associated with leaving the current state and entering the new one
/// will be invoked.
///
/// Type of the first trigger argument.
/// Type of the second trigger argument.
/// The first argument.
/// The second argument.
/// The trigger to fire.
/// The current state does
/// not allow the trigger to be fired.
public void Fire(TriggerWithParameters2 trigger, TArg0 arg0, TArg1 arg1) throws Exception
{
Enforce.ArgumentNotNull(trigger, "trigger");
publicFire(trigger.getTrigger(), arg0, arg1);
}
///
/// Transition from the current state via the specified trigger.
/// The target state is determined by the configuration of the current state.
/// Actions associated with leaving the current state and entering the new one
/// will be invoked.
///
/// Type of the first trigger argument.
/// Type of the second trigger argument.
/// Type of the third trigger argument.
/// The first argument.
/// The second argument.
/// The third argument.
/// The trigger to fire.
/// The current state does
/// not allow the trigger to be fired.
public void Fire(TriggerWithParameters3 trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2) throws Exception
{
Enforce.ArgumentNotNull(trigger, "trigger");
publicFire(trigger.getTrigger(), arg0, arg1, arg2);
}
void publicFire(TTrigger trigger, Object... args) throws Exception
{
TriggerWithParameters configuration;
if (_triggerConfiguration.containsKey(trigger)) {
configuration = _triggerConfiguration.get(trigger);
configuration.ValidateParameters(args);
}
TriggerBehaviour triggerBehaviour;
try {
triggerBehaviour = getCurrentRepresentation().TryFindHandler(trigger);
} catch (Exception e) {
_unhandledTriggerAction.doIt(getCurrentRepresentation().getUnderlyingState(), trigger);
return;
}
TState source = getState();
TState destination;
try {
destination = triggerBehaviour.ResultsInTransitionFrom(source, args);
Transition transition = new Transition(source, destination, trigger);
getCurrentRepresentation().Exit(transition);
setState(transition.getDestination());
getCurrentRepresentation().Enter(transition, args);
} catch (Exception e) {
}
}
///
/// Override the default behaviour of throwing an exception when an unhandled trigger
/// is fired.
///
/// An action to call when an unhandled trigger is fired.
public void OnUnhandledTrigger(Action2 unhandledTriggerAction) throws Exception
{
if (unhandledTriggerAction == null) throw new Exception("unhandledTriggerAction");
_unhandledTriggerAction = unhandledTriggerAction;
}
///
/// Determine if the state machine is in the supplied state.
///
/// The state to test for.
/// True if the current state is equal to, or a substate of,
/// the supplied state.
public Boolean IsInState(TState state)
{
return getCurrentRepresentation().IsIncludedIn(state);
}
///
/// Returns true if can be fired
/// in the current state.
///
/// Trigger to test.
/// True if the trigger can be fired, false otherwise.
public Boolean CanFire(TTrigger trigger)
{
return getCurrentRepresentation().CanHandle(trigger);
}
///
/// A human-readable representation of the state machine.
///
/// A description of the current state and permitted triggers.
public String toString()
{
List permittedTriggers = getPermittedTriggers();
List parameters = new ArrayList();
for (TTrigger tTrigger : permittedTriggers) {
parameters.add(tTrigger.toString());
}
return String.format(
"StateMachine {{ State = {0}, PermittedTriggers = {{ {1} }}}}",
getState(),
StringUtils.join(parameters, ", "));
}
///
/// Specify the arguments that must be supplied when a specific trigger is fired.
///
/// Type of the first trigger argument.
/// The underlying trigger value.
/// An object that can be passed to the Fire() method in order to
/// fire the parameterised trigger.
public TriggerWithParameters1 SetTriggerParameters(TTrigger trigger, Class classe0) throws Exception
{
TriggerWithParameters1 configuration = new TriggerWithParameters1(trigger, classe0);
SaveTriggerConfiguration(configuration);
return configuration;
}
///
/// Specify the arguments that must be supplied when a specific trigger is fired.
///
/// Type of the first trigger argument.
/// Type of the second trigger argument.
/// The underlying trigger value.
/// An object that can be passed to the Fire() method in order to
/// fire the parameterised trigger.
public TriggerWithParameters2 SetTriggerParameters(TTrigger trigger, Class classe0, Class classe1) throws Exception
{
TriggerWithParameters2 configuration = new TriggerWithParameters2(trigger, classe0, classe1);
SaveTriggerConfiguration(configuration);
return configuration;
}
///
/// Specify the arguments that must be supplied when a specific trigger is fired.
///
/// Type of the first trigger argument.
/// Type of the second trigger argument.
/// Type of the third trigger argument.
/// The underlying trigger value.
/// An object that can be passed to the Fire() method in order to
/// fire the parameterised trigger.
public TriggerWithParameters3 SetTriggerParameters(TTrigger trigger, Class classe0, Class classe1, Class classe2) throws Exception
{
TriggerWithParameters3 configuration = new TriggerWithParameters3(trigger, classe0, classe1, classe2);
SaveTriggerConfiguration(configuration);
return configuration;
}
void SaveTriggerConfiguration(TriggerWithParameters trigger) throws Exception
{
if (_triggerConfiguration.containsKey(trigger.getTrigger()))
throw new Exception(
String.format(StateMachineResources.CannotReconfigureParameters, trigger));
_triggerConfiguration.put(trigger.getTrigger(), trigger);
}
}