State.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. using System;
  2. using System.Globalization;
  3. using Wayne.Lib.Log;
  4. using System.Text;
  5. namespace Wayne.Lib.StateEngine
  6. {
  7. /// <summary>
  8. /// History type defines the way that a composite state is entered.
  9. /// </summary>
  10. public enum HistoryType
  11. {
  12. #region Fields
  13. /// <summary>
  14. /// Only the initial state is entered. No history is recalled
  15. /// </summary>
  16. None,
  17. /// <summary>
  18. /// If the composite has been active before, the state that was
  19. /// active last is entered. Shallow means that entering the
  20. /// recalled last state but when entering that state, it is done with
  21. /// no history.
  22. /// </summary>
  23. Shallow,
  24. /// <summary>
  25. /// If a composite state has been active before, the state that was active
  26. /// last is entered, Deep means that if the recalled state is a composite
  27. /// it will also be entered with deep history.
  28. /// </summary>
  29. Deep,
  30. /// <summary>
  31. /// Explicit history type is *only* used when issuing explicit transitions. If
  32. /// a state machine is configured with this history type, an error will be thrown.
  33. /// </summary>
  34. Explicit
  35. #endregion
  36. }
  37. // StateType for future use.
  38. /// <summary>
  39. /// The state types as an enumeration. This can be used in a future design tool.
  40. /// </summary>
  41. public enum StateType
  42. {
  43. #region Fields
  44. /// <summary>
  45. /// Intial state
  46. /// </summary>
  47. InitialState,
  48. /// <summary>
  49. /// Pseudo state
  50. /// </summary>
  51. PseudoState,
  52. /// <summary>
  53. /// Ordinary state
  54. /// </summary>
  55. State,
  56. /// <summary>
  57. /// Composite state
  58. /// </summary>
  59. CompositeState,
  60. /// <summary>
  61. /// Final state
  62. /// </summary>
  63. FinalState
  64. #endregion
  65. }
  66. /// <summary>
  67. /// State is the base state of all states in the state machine.
  68. /// </summary>
  69. abstract public class State : IDisposable
  70. {
  71. #region Fields
  72. private Wayne.Lib.StateEngine.StateMachine parentStateMachine;
  73. private string createdByFactory = "";
  74. private bool active;
  75. private CompositeState parentState;
  76. private IDebugLogger debugLogger;
  77. private object logCategory;
  78. private string instanceName;
  79. private string factoryName;
  80. #endregion
  81. #region Methods
  82. /// <summary>
  83. /// Enter is called when the state machine enters the state. Override this method to be able to
  84. /// run code at the state entry. If a transition should be performed, create a transition object
  85. /// and return it in the transition out property.
  86. /// </summary>
  87. /// <param name="stateEntry">Information about the entry of the state.</param>
  88. /// <param name="transition">Out parameter, that should be set to either the reference to a transition object or null.</param>
  89. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")]
  90. protected virtual void Enter(StateEntry stateEntry, ref Transition transition)
  91. {
  92. }
  93. /// <summary>
  94. /// Override this method to implement code that should be run at state exit.
  95. /// </summary>
  96. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
  97. protected virtual void Exit()
  98. {
  99. }
  100. /// <summary>
  101. /// Override to receive incoming events. If the event is handled, the
  102. /// application must set the event.Handled = true.
  103. /// </summary>
  104. /// <param name="stateEngineEvent">The event object that should be handled.</param>
  105. /// <param name="transition">Out parameter that should be set to either the reference to a transition object or null.</param>
  106. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")]
  107. protected virtual void HandleEvent(StateEngineEvent stateEngineEvent, ref Transition transition)
  108. {
  109. }
  110. /// <summary>
  111. /// Activates the supplied timer.
  112. /// </summary>
  113. /// <param name="timer">The timer to activate.</param>
  114. /// <see cref="Timer"/>
  115. protected virtual void ActivateTimer(Timer timer)
  116. {
  117. if (parentStateMachine != null)
  118. parentStateMachine.ActivateTimer(timer);
  119. }
  120. /// <summary>
  121. /// Disposes the resources owned by the state object.
  122. /// </summary>
  123. /// <param name="disposing"></param>
  124. protected virtual void Dispose(bool disposing)
  125. {
  126. //Nothing in an ordinary state.
  127. }
  128. /// <summary>
  129. /// Clears all events waiting in the event queues.
  130. /// </summary>
  131. protected void ClearPendingEvents()
  132. {
  133. this.parentStateMachine.ClearPendingEvents();
  134. }
  135. /// <summary>
  136. /// Clears all the events in the resend queue that matches the eventType.
  137. /// </summary>
  138. /// <param name="eventType"></param>
  139. protected void RemovePendingEventsOfType(object eventType)
  140. {
  141. this.parentStateMachine.RemovePendingEventsOfType(eventType);
  142. }
  143. /// <summary>
  144. /// Removes all pending event that matches the supplied predicate.
  145. /// </summary>
  146. /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam>
  147. /// <param name="predicate">The predicate that is used to match the event.</param>
  148. /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param>
  149. protected void RemovePendingEvents<TComparisonObject>(StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject)
  150. {
  151. this.ParentStateMachine.RemovePendingEvents(predicate, comparisonObject);
  152. }
  153. /// <summary>
  154. /// Disposes all the owned resources in the state.
  155. /// </summary>
  156. public void Dispose()
  157. {
  158. Dispose(true);
  159. }
  160. #endregion
  161. #region Internal Methods
  162. /// <summary>
  163. /// Incoming event is called from the state machine when an event should be handled.
  164. /// </summary>
  165. /// <param name="stateEngineEvent"></param>
  166. /// <param name="transition">Perform a transition by assigning a transition object to this reference parameter.</param>
  167. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  168. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
  169. internal virtual void IncomingEvent(StateEngineEvent stateEngineEvent, ref Transition transition)
  170. {
  171. try
  172. {
  173. if (!(this is CompositeState))
  174. {
  175. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  176. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " [{0}] HandleEvent: {1}", this.LogName, stateEngineEvent), logCategory);
  177. }
  178. if (!stateEngineEvent.Handled)
  179. {
  180. //Call the HandleEvent method of the state
  181. HandleEvent(stateEngineEvent, ref transition);
  182. }
  183. //If a transition was issued, equip it with the event as source event.
  184. if (transition != null)
  185. transition.WritableSourceEvent = stateEngineEvent;
  186. }
  187. catch (Exception exception)
  188. {
  189. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  190. {
  191. System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
  192. stringBuilder.Append("EXCEPTION IN HandleEvent (");
  193. stringBuilder.Append(this.LogName);
  194. stringBuilder.Append(")\r\n");
  195. stringBuilder.Append(exception.ToString());
  196. debugLogger.Add(stringBuilder, logCategory);
  197. }
  198. transition = new ExceptionTransition(this, BasicTransitionType.Error, exception);
  199. }
  200. #if WindowsCE
  201. catch
  202. {
  203. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  204. {
  205. debugLogger.Add("Unknown exception in HandleEvent (" + this.LogName + ")");
  206. }
  207. transition = new Transition(this, BasicTransitionType.Error);
  208. }
  209. #endif
  210. }
  211. /// <summary>
  212. /// Called from the state machine when the state is entered. It is up to the state to
  213. /// call the appropriate virtual Enter methods. If a transition should be performed it
  214. /// is returned in the Transition ref parameter.
  215. /// </summary>
  216. /// <param name="stateEntry">State entry object containing information about the state entry.</param>
  217. /// <param name="transition">Perform a transition by assigning a transition object to this ref parameter.</param>
  218. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  219. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
  220. internal virtual void PerformEnter(StateEntry stateEntry, ref Transition transition)
  221. {
  222. active = true;
  223. try
  224. {
  225. if (!(this is CompositeState))
  226. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  227. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " [{0}] Enter: Transition={1}", this.LogName, stateEntry.SourceTransition.Name), logCategory);
  228. //Call the enter method of the state.
  229. Enter(stateEntry, ref transition);
  230. //If a transition was issued, equip it with the event as source event.
  231. if (transition != null)
  232. if ((stateEntry != null) && (stateEntry.SourceTransition != null))
  233. transition.WritableSourceEvent = stateEntry.SourceTransition.SourceEvent;
  234. }
  235. catch (Exception exception)
  236. {
  237. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  238. {
  239. System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
  240. stringBuilder.Append("EXCEPTION IN Enter (");
  241. stringBuilder.Append(this.LogName);
  242. stringBuilder.Append(")\r\n");
  243. stringBuilder.Append(exception.ToString());
  244. debugLogger.Add(stringBuilder.ToString(), logCategory);
  245. }
  246. transition = new ExceptionTransition(this, BasicTransitionType.Error, exception);
  247. }
  248. #if WindowsCE
  249. catch
  250. {
  251. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  252. debugLogger.Add("Unknown exception in Enter (" + this.LogName + ")");
  253. transition = new Transition(this, BasicTransitionType.Error);
  254. }
  255. #endif
  256. }
  257. /// <summary>
  258. /// This method is called by the state machine when the state should be exited. It contains the error handling
  259. /// logic and logging of the exit.
  260. /// </summary>
  261. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  262. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "Wayne.Lib.StateEngine.State.DebugLog(System.String)")]
  263. internal virtual void PerformExit()
  264. {
  265. active = false;
  266. try
  267. {
  268. if (!(this is CompositeState))
  269. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  270. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " [{0}] Exit", this.LogName), logCategory);
  271. Exit();
  272. }
  273. catch (Exception exception)
  274. {
  275. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  276. {
  277. System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
  278. stringBuilder.Append("EXCEPTION IN Exit (");
  279. stringBuilder.Append(this.LogName);
  280. stringBuilder.Append(")\r\n");
  281. stringBuilder.Append(exception.ToString());
  282. debugLogger.Add(stringBuilder, logCategory);
  283. }
  284. }
  285. #if WindowsCE
  286. catch
  287. {
  288. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  289. debugLogger.Add("Unknown exception in Exit (" + this.LogName + ")");
  290. }
  291. #endif
  292. }
  293. /// <summary>
  294. /// This function is used to locate a specific state in the state machine.
  295. /// In the state class it is simply compared to the state Factory Name. More complex comparisons
  296. /// is done in composite states.
  297. /// </summary>
  298. /// <param name="stateFactoryName">The factory name of the state that should be looked up.</param>
  299. /// <returns>True if the state is or contains a state with this name.</returns>
  300. internal virtual bool LookupState(string stateFactoryName)
  301. {
  302. return (stateFactoryName == this.FactoryName);
  303. }
  304. internal void AssignParentState(CompositeState parentState)
  305. {
  306. this.parentState = parentState;
  307. }
  308. #endregion
  309. #region Properties
  310. /// <summary>
  311. /// The factory name of the state (the full class name).
  312. /// </summary>
  313. public string FactoryName
  314. {
  315. get
  316. {
  317. return factoryName;
  318. }
  319. }
  320. /// <summary>
  321. /// Factory name of the class assigned after factory created the state.
  322. /// </summary>
  323. internal string WritableFactoryName
  324. {
  325. get { return factoryName; }
  326. set { this.factoryName = value; }
  327. }
  328. /// <summary>
  329. /// The name of this particular instance of this state (the hierarchical name of the state,
  330. /// starting with the name of the statemachine, through all parent composite states up to this state).
  331. /// </summary>
  332. public string InstanceName
  333. {
  334. get
  335. {
  336. if (instanceName == null)
  337. {
  338. StringBuilder instanceNameBuilder = new StringBuilder();
  339. if (this is CompositeState)
  340. {
  341. string compositeNamespace = GetType().Namespace;
  342. int lastDotPosition = compositeNamespace.LastIndexOf('.');
  343. if (lastDotPosition > 0)
  344. {
  345. string lastPartOfNamespace = compositeNamespace.Substring(lastDotPosition + 1);
  346. instanceNameBuilder.Append(lastPartOfNamespace);
  347. instanceNameBuilder.Append(".");
  348. }
  349. }
  350. instanceNameBuilder.Append(GetType().Name);
  351. if (parentState != null)
  352. {
  353. CompositeState tempParent = parentState;
  354. while (tempParent != null)
  355. {
  356. string compositeNamespace = tempParent.GetType().Namespace;
  357. int lastDotPosition = compositeNamespace.LastIndexOf('.');
  358. if (lastDotPosition > 0)
  359. {
  360. string lastPartOfNamespace = compositeNamespace.Substring(lastDotPosition + 1);
  361. instanceNameBuilder.Insert(0, ".");
  362. instanceNameBuilder.Insert(0, lastPartOfNamespace);
  363. }
  364. else
  365. {
  366. instanceNameBuilder.Insert(0, ".");
  367. instanceNameBuilder.Insert(0, tempParent.GetType().Name);
  368. }
  369. if (tempParent.parentState == null)
  370. {
  371. instanceNameBuilder.Insert(0, ".");
  372. instanceNameBuilder.Insert(0, tempParent.parentStateMachine.Name);
  373. }
  374. tempParent = tempParent.parentState;
  375. }
  376. }
  377. else if (parentStateMachine != null)
  378. {
  379. instanceNameBuilder.Insert(0, ".");
  380. instanceNameBuilder.Insert(0, parentStateMachine.Name);
  381. }
  382. instanceName = instanceNameBuilder.ToString();
  383. }
  384. return instanceName;
  385. }
  386. }
  387. /// <summary>
  388. /// The name of the state used for logging.
  389. /// </summary>
  390. public string LogName
  391. {
  392. get
  393. {
  394. if (parentStateMachine != null)
  395. {
  396. switch (parentStateMachine.LogNameKind)
  397. {
  398. case StateNameKind.FactoryName: return FactoryName;
  399. case StateNameKind.InstanceName: return InstanceName;
  400. }
  401. }
  402. return FactoryName;
  403. }
  404. }
  405. /// <summary>
  406. /// Name of the state factory that created the state object.
  407. /// </summary>
  408. public string CreatedByFactory
  409. {
  410. get
  411. {
  412. return createdByFactory;
  413. }
  414. set
  415. {
  416. createdByFactory = value;
  417. }
  418. }
  419. /// <summary>
  420. /// Indicates that this is the current active state of the machine.
  421. /// </summary>
  422. public bool Active
  423. {
  424. get
  425. {
  426. return active;
  427. }
  428. }
  429. /// <summary>
  430. /// Provides a reference to the composite state this state is contained in. If it is in the root of the
  431. /// state machine, it will be null.
  432. /// </summary>
  433. public CompositeState ParentState
  434. {
  435. get { return parentState; }
  436. }
  437. /// <summary>
  438. /// The StateType.
  439. /// </summary>
  440. public StateType StateType
  441. {
  442. get
  443. {
  444. if (this is InitialState)
  445. return StateType.InitialState;
  446. else if (this is FinalState)
  447. return StateType.FinalState;
  448. else if (this is PseudoState)
  449. return StateType.PseudoState;
  450. else if (this is CompositeState)
  451. return StateType.CompositeState;
  452. return StateType.State;
  453. }
  454. }
  455. /// <summary>
  456. /// The parent state machine for the state.
  457. /// </summary>
  458. public StateMachine ParentStateMachine
  459. {
  460. get { return parentStateMachine; }
  461. }
  462. /// <summary>
  463. /// An additional text that shows up in the visualizer, that for instance can
  464. /// be used to point out application specific states.
  465. /// </summary>
  466. public virtual string ApplicationText
  467. {
  468. get;
  469. set;
  470. }
  471. #endregion
  472. #region Internal properties
  473. /// <summary>
  474. /// Set the parent state machine for the state.
  475. /// </summary>
  476. internal virtual void SetParentStateMachine(StateMachine parentStateMachine)
  477. {
  478. this.parentStateMachine = parentStateMachine;
  479. }
  480. /// <summary>
  481. /// Sets the debug logger.
  482. /// </summary>
  483. /// <param name="debugLogger"></param>
  484. /// <param name="logCategory"></param>
  485. internal virtual void SetDebugLogger(IDebugLogger debugLogger, object logCategory)
  486. {
  487. this.debugLogger = debugLogger;
  488. this.logCategory = logCategory;
  489. }
  490. #endregion
  491. /// <summary>
  492. /// Uses the debug logger passed in to the StateMachine upon creation to
  493. /// log the supplied logging if the logger is active.
  494. /// </summary>
  495. /// <param name="format"></param>
  496. /// <param name="params"></param>
  497. protected void DebugLog(string format, params object[] @params)
  498. {
  499. if (string.IsNullOrEmpty(format) || format == " ") return;
  500. try
  501. {
  502. if (debugLogger.IsActive())
  503. debugLogger.Add(string.Format(CultureInfo.InvariantCulture, format, @params));
  504. }
  505. catch (FormatException)
  506. {
  507. if (debugLogger.IsActive())
  508. debugLogger.Add("LOGERROR: Could not format string: " + format);
  509. }
  510. }
  511. /// <summary>
  512. /// Uses the debug logger passed in to the StateMachine upon creation to
  513. /// log the supplied logging if the logger is active.
  514. /// </summary>
  515. /// <param name="format"></param>
  516. /// <param name="params"></param>
  517. protected void DebugLogDetailed(string format, params object[] @params)
  518. {
  519. try
  520. {
  521. if (debugLogger.IsActive(DebugLogLevel.Detailed))
  522. debugLogger.Add(string.Format(CultureInfo.InvariantCulture, format, @params),DebugLogLevel.Detailed);
  523. }
  524. catch (FormatException)
  525. {
  526. if (debugLogger.IsActive(DebugLogLevel.Detailed))
  527. debugLogger.Add("LOGERROR: Could not format string: " + format, DebugLogLevel.Detailed);
  528. }
  529. }
  530. /// <summary>
  531. /// Uses the debug logger passed in to the StateMachine upon creation to
  532. /// log the supplied logging if the logger is active.
  533. /// </summary>
  534. /// <param name="format"></param>
  535. /// <param name="params"></param>
  536. protected void DebugLogMaximized(string format, params object[] @params)
  537. {
  538. try
  539. {
  540. if (debugLogger.IsActive(DebugLogLevel.Detailed))
  541. debugLogger.Add(string.Format(CultureInfo.InvariantCulture, format, @params), DebugLogLevel.Maximized);
  542. }
  543. catch (FormatException)
  544. {
  545. if (debugLogger.IsActive(DebugLogLevel.Detailed))
  546. debugLogger.Add("LOGERROR: Could not format string: " + format, DebugLogLevel.Maximized);
  547. }
  548. }
  549. }
  550. }