StateData.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. namespace Wayne.Lib.StateEngine.Generic
  5. {
  6. /// <summary>
  7. /// Class that is used as base class for State Data objects. The idea is that the descendant classes
  8. /// should define strongly typed properties for the data that should be accessed from the states. The
  9. /// storage of the data however is in a weakly typed dictionary that is managed by the class through the
  10. /// Define/Get/Set data methods.
  11. /// Typically an enumeration should be used to define the data.
  12. /// </summary>
  13. public abstract class StateData : IDisposable
  14. {
  15. #region Fields
  16. private readonly StateData parentStateData;
  17. private readonly Dictionary<object, object> dataDict = new Dictionary<object, object>();
  18. private readonly Dictionary<object, object> dataDefinitionDict = new Dictionary<object, object>();
  19. private readonly Dictionary<object, Type> dataDefinitionType = new Dictionary<object, Type>();
  20. #endregion
  21. #region Construction
  22. /// <summary>
  23. /// Constructor
  24. /// </summary>
  25. /// <param name="parentStateData">Reference to the parent composite state's state data object.</param>
  26. protected StateData(StateData parentStateData)
  27. {
  28. this.parentStateData = parentStateData;
  29. }
  30. /// <summary>
  31. /// Finalizer
  32. /// </summary>
  33. ~StateData()
  34. {
  35. Dispose(false);
  36. }
  37. #endregion
  38. #region Protected Methods
  39. /// <summary>
  40. /// Defines a parameter and specifies the type and initial value for it.
  41. /// </summary>
  42. /// <param name="dataDefinition"></param>
  43. /// <param name="initialValue"></param>
  44. protected void DefineData<TDefinitionType, TDataType>(TDefinitionType dataDefinition, TDataType initialValue)
  45. {
  46. if (!DataIsDefined(dataDefinition))
  47. {
  48. dataDict[dataDefinition] = initialValue;
  49. dataDefinitionDict[dataDefinition] = initialValue;
  50. dataDefinitionType[dataDefinition] = typeof(TDataType);
  51. }
  52. }
  53. /// <summary>
  54. /// Checks if the data is defined already in a parent state data.
  55. /// </summary>
  56. /// <param name="dataDefinition"></param>
  57. /// <returns></returns>
  58. private bool DataIsDefined(object dataDefinition)
  59. {
  60. if (dataDict.ContainsKey(dataDefinition))
  61. return true;
  62. else if (parentStateData != null)
  63. return parentStateData.DataIsDefined(dataDefinition);
  64. else
  65. return false;
  66. }
  67. /// <summary>
  68. /// Sets a new value to a field that is defined by dataDefinition.
  69. /// </summary>
  70. /// <param name="dataDefinition"></param>
  71. /// <param name="dataField"></param>
  72. protected void SetData<TDefinitionType, TDataType>(TDefinitionType dataDefinition, TDataType dataField)
  73. {
  74. //TDataType oldValue = GetData<TDefinitionType, TDataType>(dataDefinition);
  75. // Console.WriteLine("{0}++++ {1}.SetData ({2})={3}=>{4}", DateTime.Now.ToString("HH:mm:ss fff"), this.GetType(), dataDefinition, oldValue, dataField);
  76. if (!InternalSetData(dataDefinition, dataField))
  77. throw new StateEngineException("Unable to set data " + dataDefinition);
  78. }
  79. /// <summary>
  80. /// Gets data from the definition.
  81. /// </summary>
  82. /// <param name="dataDefinition"></param>
  83. /// <returns></returns>
  84. public TDataType GetData<TDefinitionType, TDataType>(TDefinitionType dataDefinition)
  85. {
  86. TDataType result;
  87. if (InternalGetData(dataDefinition, out result))
  88. {
  89. //Console.WriteLine("---- {0}.GetData ({1})={2}", this.GetType(), dataDefinition, result);
  90. return result;
  91. }
  92. else
  93. throw new StateEngineException("Unable to get data " + dataDefinition);
  94. }
  95. #endregion
  96. #region Internal Methods
  97. private bool InternalSetData<TDefinitionType, TDataType>(TDefinitionType dataDefinition, TDataType dataField)
  98. {
  99. if (dataDict.ContainsKey(dataDefinition))
  100. {
  101. //Validate the type of the value that is already there.
  102. //It may
  103. // 1. Be of the exact same type as the data was defined
  104. // 2. It may be a descendent class to the defined.
  105. object initialValue = dataDefinitionDict[dataDefinition];
  106. if (dataDefinitionType[dataDefinition].IsAssignableFrom(typeof(TDataType)))
  107. dataDict[dataDefinition] = dataField;
  108. else
  109. throw new InvalidCastException(string.Concat("Cannot set data with definition ", dataDefinition, " as type ", typeof(TDataType).Name, "; should be ", initialValue.GetType().Name));
  110. return true;
  111. }
  112. else if (parentStateData != null)
  113. {
  114. return parentStateData.InternalSetData(dataDefinition, dataField);
  115. }
  116. else
  117. {
  118. return false;
  119. }
  120. }
  121. private bool InternalGetData<TDefinitionType, TDataType>(TDefinitionType dataDefinition, out TDataType dataField)
  122. {
  123. if (dataDict.ContainsKey(dataDefinition))
  124. {
  125. object value = dataDict[dataDefinition];
  126. Type type = dataDefinitionType[dataDefinition];
  127. //It's ok to get data as long the data is of the requested type.
  128. if (type.Equals(typeof(TDataType)))
  129. {
  130. dataField = (TDataType)value;
  131. return true;
  132. }
  133. else
  134. {
  135. throw new InvalidCastException(string.Concat("Cannot get data with definition ", dataDefinition,
  136. " as type ", typeof(TDataType).Name, "; is ",
  137. value.GetType().Name));
  138. }
  139. }
  140. else if (parentStateData != null)
  141. {
  142. return parentStateData.InternalGetData(dataDefinition, out dataField);
  143. }
  144. else
  145. {
  146. dataField = default(TDataType);
  147. return false;
  148. }
  149. }
  150. /// <summary>
  151. /// Used from the different generic data states to handle fetch the Composite state that contains the state data that this state should use.
  152. /// </summary>
  153. /// <typeparam name="TData"></typeparam>
  154. /// <param name="state"></param>
  155. /// <returns></returns>
  156. internal static IStateWithData GetParentCompositeStateWithStateData<TData>(State state) where TData : class
  157. {
  158. CompositeState compositeState = state.ParentState;
  159. while (compositeState != null)
  160. {
  161. IStateWithData stateWithData = compositeState as IStateWithData;
  162. if (stateWithData != null)
  163. {
  164. if (stateWithData.StateData is TData)
  165. {
  166. return stateWithData;
  167. }
  168. }
  169. //Search down the inheritance line.
  170. compositeState = compositeState.ParentState;
  171. }
  172. throw new StateEngineException("State with state data of type " + typeof(TData) + " is not contained in any matching composite state.");
  173. }
  174. #endregion
  175. /// <summary>
  176. /// Dispose
  177. /// </summary>
  178. /// <param name="disposing"></param>
  179. protected virtual void Dispose(bool disposing)
  180. {
  181. }
  182. /// <summary>
  183. /// Dispose
  184. /// </summary>
  185. public void Dispose()
  186. {
  187. Dispose(true);
  188. GC.SuppressFinalize(this);
  189. }
  190. }
  191. }