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); } } } }