using System;
using System.Globalization;
using Wayne.Lib.Log;
using System.Text;
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
{
if (!(this is CompositeState))
if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " [{0}] Exit", this.LogName), logCategory);
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)
{
StringBuilder instanceNameBuilder = new StringBuilder();
if (this is CompositeState)
{
string compositeNamespace = GetType().Namespace;
int lastDotPosition = compositeNamespace.LastIndexOf('.');
if (lastDotPosition > 0)
{
string lastPartOfNamespace = compositeNamespace.Substring(lastDotPosition + 1);
instanceNameBuilder.Append(lastPartOfNamespace);
instanceNameBuilder.Append(".");
}
}
instanceNameBuilder.Append(GetType().Name);
if (parentState != null)
{
CompositeState tempParent = parentState;
while (tempParent != null)
{
string compositeNamespace = tempParent.GetType().Namespace;
int lastDotPosition = compositeNamespace.LastIndexOf('.');
if (lastDotPosition > 0)
{
string lastPartOfNamespace = compositeNamespace.Substring(lastDotPosition + 1);
instanceNameBuilder.Insert(0, ".");
instanceNameBuilder.Insert(0, lastPartOfNamespace);
}
else
{
instanceNameBuilder.Insert(0, ".");
instanceNameBuilder.Insert(0, tempParent.GetType().Name);
}
if (tempParent.parentState == null)
{
instanceNameBuilder.Insert(0, ".");
instanceNameBuilder.Insert(0, tempParent.parentStateMachine.Name);
}
tempParent = tempParent.parentState;
}
}
else if (parentStateMachine != null)
{
instanceNameBuilder.Insert(0, ".");
instanceNameBuilder.Insert(0, parentStateMachine.Name);
}
instanceName = instanceNameBuilder.ToString();
}
return instanceName;
}
}
///
/// 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)
{
if (string.IsNullOrEmpty(format) || format == " ") return;
try
{
if (debugLogger.IsActive())
debugLogger.Add(string.Format(CultureInfo.InvariantCulture, format, @params));
}
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))
debugLogger.Add(string.Format(CultureInfo.InvariantCulture, format, @params),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))
debugLogger.Add(string.Format(CultureInfo.InvariantCulture, format, @params), DebugLogLevel.Maximized);
}
catch (FormatException)
{
if (debugLogger.IsActive(DebugLogLevel.Detailed))
debugLogger.Add("LOGERROR: Could not format string: " + format, DebugLogLevel.Maximized);
}
}
}
}