StateData.cs 7.9 KB

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