using System;
using System.Globalization;
using Wayne.Lib.Log;
namespace Wayne.Lib.StateEngine
{
///
/// History type defines the way that a composite state is entered.
///
public enum HistoryType
{
#region Fields
///
/// Only the initial state is entered. No history is recalled
///
None,
///
/// If the composite has been active before, the state that was
/// active last is entered. Shallow means that entering the
/// recalled last state but when entering that state, it is done with
/// no history.
///
Shallow,
///
/// If a composite state has been active before, the state that was active
/// last is entered, Deep means that if the recalled state is a composite
/// it will also be entered with deep history.
///
Deep,
///
/// Explicit history type is *only* used when issuing explicit transitions. If
/// a state machine is configured with this history type, an error will be thrown.
///
Explicit
#endregion
}
// StateType for future use.
///
/// The state types as an enumeration. This can be used in a future design tool.
///
public enum StateType
{
#region Fields
///
/// Intial state
///
InitialState,
///
/// Pseudo state
///
PseudoState,
///
/// Ordinary state
///
State,
///
/// Composite state
///
CompositeState,
///
/// Final state
///
FinalState
#endregion
}
///
/// State is the base state of all states in the state machine.
///
abstract public class State : IDisposable
{
#region Fields
private Wayne.Lib.StateEngine.StateMachine parentStateMachine;
private string createdByFactory = "";
private bool active;
private CompositeState parentState;
private IDebugLogger debugLogger;
private object logCategory;
private string instanceName;
private string factoryName;
#endregion
#region Methods
///
/// Enter is called when the state machine enters the state. Override this method to be able to
/// run code at the state entry. If a transition should be performed, create a transition object
/// and return it in the transition out property.
///
/// Information about the entry of the state.
/// Out parameter, that should be set to either the reference to a transition object or null.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")]
protected virtual void Enter(StateEntry stateEntry, ref Transition transition)
{
}
///
/// Override this method to implement code that should be run at state exit.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
protected virtual void Exit()
{
}
///
/// Override to receive incoming events. If the event is handled, the
/// application must set the event.Handled = true.
///
/// The event object that should be handled.
/// Out parameter that should be set to either the reference to a transition object or null.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")]
protected virtual void HandleEvent(StateEngineEvent stateEngineEvent, ref Transition transition)
{
}
///
/// Activates the supplied timer.
///
/// The timer to activate.
///
protected virtual void ActivateTimer(Timer timer)
{
if (parentStateMachine != null)
parentStateMachine.ActivateTimer(timer);
}
///
/// Disposes the resources owned by the state object.
///
///
protected virtual void Dispose(bool disposing)
{
//Nothing in an ordinary state.
}
///
/// Clears all events waiting in the event queues.
///
protected void ClearPendingEvents()
{
this.parentStateMachine.ClearPendingEvents();
}
///
/// Clears all the events in the resend queue that matches the eventType.
///
///
protected void RemovePendingEventsOfType(object eventType)
{
this.parentStateMachine.RemovePendingEventsOfType(eventType);
}
///
/// Removes all pending event that matches the supplied predicate.
///
/// Type of the comparison object
/// The predicate that is used to match the event.
/// The comparison object that is used in the StateEngineEventPredicate.
protected void RemovePendingEvents(StateEngineEventPredicate predicate, TComparisonObject comparisonObject)
{
this.ParentStateMachine.RemovePendingEvents(predicate, comparisonObject);
}
///
/// Disposes all the owned resources in the state.
///
public void Dispose()
{
Dispose(true);
}
#endregion
#region Internal Methods
///
/// Incoming event is called from the state machine when an event should be handled.
///
///
/// Perform a transition by assigning a transition object to this reference parameter.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
internal virtual void IncomingEvent(StateEngineEvent stateEngineEvent, ref Transition transition)
{
try
{
if (!(this is CompositeState))
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " [{0}] HandleEvent: {1}", this.LogName, stateEngineEvent), logCategory);
}
if (!stateEngineEvent.Handled)
{
//Call the HandleEvent method of the state
HandleEvent(stateEngineEvent, ref transition);
}
//If a transition was issued, equip it with the event as source event.
if (transition != null)
transition.WritableSourceEvent = stateEngineEvent;
}
catch (Exception exception)
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
{
System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
stringBuilder.Append("EXCEPTION IN HandleEvent (");
stringBuilder.Append(this.LogName);
stringBuilder.Append(")\r\n");
stringBuilder.Append(exception.ToString());
debugLogger.Add(stringBuilder, logCategory);
}
transition = new ExceptionTransition(this, BasicTransitionType.Error, exception);
}
#if WindowsCE
catch
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
{
debugLogger.Add("Unknown exception in HandleEvent (" + this.LogName + ")");
}
transition = new Transition(this, BasicTransitionType.Error);
}
#endif
}
///
/// Called from the state machine when the state is entered. It is up to the state to
/// call the appropriate virtual Enter methods. If a transition should be performed it
/// is returned in the Transition ref parameter.
///
/// State entry object containing information about the state entry.
/// Perform a transition by assigning a transition object to this ref parameter.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
internal virtual void PerformEnter(StateEntry stateEntry, ref Transition transition)
{
active = true;
try
{
if (!(this is CompositeState))
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " [{0}] Enter: Transition={1}", this.LogName, stateEntry.SourceTransition.Name), logCategory);
//Call the enter method of the state.
Enter(stateEntry, ref transition);
//If a transition was issued, equip it with the event as source event.
if (transition != null)
if ((stateEntry != null) && (stateEntry.SourceTransition != null))
transition.WritableSourceEvent = stateEntry.SourceTransition.SourceEvent;
}
catch (Exception exception)
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
{
System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
stringBuilder.Append("EXCEPTION IN Enter (");
stringBuilder.Append(this.LogName);
stringBuilder.Append(")\r\n");
stringBuilder.Append(exception.ToString());
debugLogger.Add(stringBuilder.ToString(), logCategory);
}
transition = new ExceptionTransition(this, BasicTransitionType.Error, exception);
}
#if WindowsCE
catch
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
debugLogger.Add("Unknown exception in Enter (" + this.LogName + ")");
transition = new Transition(this, BasicTransitionType.Error);
}
#endif
}
///
/// This method is called by the state machine when the state should be exited. It contains the error handling
/// logic and logging of the exit.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
internal virtual void PerformExit()
{
active = false;
try
{
Exit();
}
catch (Exception exception)
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
{
System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
stringBuilder.Append("EXCEPTION IN Exit (");
stringBuilder.Append(this.LogName);
stringBuilder.Append(")\r\n");
stringBuilder.Append(exception.ToString());
debugLogger.Add(stringBuilder, logCategory);
}
}
#if WindowsCE
catch
{
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
debugLogger.Add("Unknown exception in Exit (" + this.LogName + ")");
}
#endif
}
///
/// This function is used to locate a specific state in the state machine.
/// In the state class it is simply compared to the state Factory Name. More complex comparisons
/// is done in composite states.
///
/// The factory name of the state that should be looked up.
/// True if the state is or contains a state with this name.
internal virtual bool LookupState(string stateFactoryName)
{
return (stateFactoryName == this.FactoryName);
}
internal void AssignParentState(CompositeState parentState)
{
this.parentState = parentState;
}
#endregion
#region Properties
///
/// The factory name of the state (the full class name).
///
public string FactoryName
{
get
{
return factoryName;
}
}
///
/// Factory name of the class assigned after factory created the state.
///
internal string WritableFactoryName
{
get { return factoryName; }
set { this.factoryName = value; }
}
///
/// The name of this particular instance of this state (the hierarchical name of the state,
/// starting with the name of the statemachine, through all parent composite states up to this state).
///
public string InstanceName
{
get
{
if (instanceName != null)
{
return instanceName;
}
//I have a parent state - use that and append my name
if (parentState != null)
{
return instanceName = string.Format("{0}.{1}", parentState.InstanceName, this.Name);
}
//I'm on the root - just use state machine name and append my name
return instanceName = string.Format("{0}.{1}", ParentStateMachine.Name, Name);
}
}
public virtual string Name
{
get { return this.GetType().Name; }
}
///
/// The name of the state used for logging.
///
public string LogName
{
get
{
if (parentStateMachine != null)
{
switch (parentStateMachine.LogNameKind)
{
case StateNameKind.FactoryName: return FactoryName;
case StateNameKind.InstanceName: return InstanceName;
}
}
return FactoryName;
}
}
///
/// Name of the state factory that created the state object.
///
public string CreatedByFactory
{
get
{
return createdByFactory;
}
set
{
createdByFactory = value;
}
}
///
/// Indicates that this is the current active state of the machine.
///
public bool Active
{
get
{
return active;
}
}
///
/// Provides a reference to the composite state this state is contained in. If it is in the root of the
/// state machine, it will be null.
///
public CompositeState ParentState
{
get { return parentState; }
}
///
/// The StateType.
///
public StateType StateType
{
get
{
if (this is InitialState)
return StateType.InitialState;
else if (this is FinalState)
return StateType.FinalState;
else if (this is PseudoState)
return StateType.PseudoState;
else if (this is CompositeState)
return StateType.CompositeState;
return StateType.State;
}
}
///
/// The parent state machine for the state.
///
public StateMachine ParentStateMachine
{
get { return parentStateMachine; }
}
///
/// An additional text that shows up in the visualizer, that for instance can
/// be used to point out application specific states.
///
public virtual string ApplicationText
{
get;
set;
}
#endregion
#region Internal properties
///
/// Set the parent state machine for the state.
///
internal virtual void SetParentStateMachine(StateMachine parentStateMachine)
{
this.parentStateMachine = parentStateMachine;
}
///
/// Sets the debug logger.
///
///
///
internal virtual void SetDebugLogger(IDebugLogger debugLogger, object logCategory)
{
this.debugLogger = debugLogger;
this.logCategory = logCategory;
}
#endregion
///
/// Uses the debug logger passed in to the StateMachine upon creation to
/// log the supplied logging if the logger is active.
///
///
///
protected void DebugLog(string format, params object[] @params)
{
try
{
if (debugLogger.IsActive())
{
var message = @params != null && @params.Length > 0 ?
string.Format(CultureInfo.InvariantCulture, format, @params) :
format;
debugLogger.Add(message);
}
}
catch (FormatException)
{
if (debugLogger.IsActive())
debugLogger.Add("LOGERROR: Could not format string: " + format);
}
}
///
/// Uses the debug logger passed in to the StateMachine upon creation to
/// log the supplied logging if the logger is active.
///
///
///
protected void DebugLogDetailed(string format, params object[] @params)
{
try
{
if (debugLogger.IsActive(DebugLogLevel.Detailed))
{
var message = @params != null && @params.Length > 0 ?
string.Format(CultureInfo.InvariantCulture, format, @params) :
format;
debugLogger.Add(message, DebugLogLevel.Detailed);
}
}
catch (FormatException)
{
if (debugLogger.IsActive(DebugLogLevel.Detailed))
debugLogger.Add("LOGERROR: Could not format string: " + format, DebugLogLevel.Detailed);
}
}
///
/// Uses the debug logger passed in to the StateMachine upon creation to
/// log the supplied logging if the logger is active.
///
///
///
protected void DebugLogMaximized(string format, params object[] @params)
{
try
{
if (debugLogger.IsActive(DebugLogLevel.Detailed))
{
var message = @params != null && @params.Length > 0 ?
string.Format(CultureInfo.InvariantCulture, format, @params) :
format;
debugLogger.Add(message, DebugLogLevel.Maximized);
}
}
catch (FormatException)
{
if (debugLogger.IsActive(DebugLogLevel.Detailed))
debugLogger.Add("LOGERROR: Could not format string: " + format, DebugLogLevel.Maximized);
}
}
}
}