using System; using System.Collections.Generic; using System.Diagnostics; namespace Wayne.Lib.StateEngine.Generic { /// /// Class that is used as base class for State Data objects. The idea is that the descendant classes /// should define strongly typed properties for the data that should be accessed from the states. The /// storage of the data however is in a weakly typed dictionary that is managed by the class through the /// Define/Get/Set data methods. /// Typically an enumeration should be used to define the data. /// public abstract class StateData : IDisposable { #region Fields private readonly StateData parentStateData; private readonly Dictionary dataDict = new Dictionary(); private readonly Dictionary dataDefinitionDict = new Dictionary(); private readonly Dictionary dataDefinitionType = new Dictionary(); #endregion #region Construction /// /// Constructor /// /// Reference to the parent composite state's state data object. protected StateData(StateData parentStateData) { this.parentStateData = parentStateData; } /// /// Finalizer /// ~StateData() { Dispose(false); } #endregion #region Protected Methods /// /// Defines a parameter and specifies the type and initial value for it. /// /// /// protected void DefineData(TDefinitionType dataDefinition, TDataType initialValue) { if (!DataIsDefined(dataDefinition)) { dataDict[dataDefinition] = initialValue; dataDefinitionDict[dataDefinition] = initialValue; dataDefinitionType[dataDefinition] = typeof(TDataType); } } /// /// Checks if the data is defined already in a parent state data. /// /// /// private bool DataIsDefined(object dataDefinition) { if (dataDict.ContainsKey(dataDefinition)) return true; else if (parentStateData != null) return parentStateData.DataIsDefined(dataDefinition); else return false; } /// /// Sets a new value to a field that is defined by dataDefinition. /// /// /// protected void SetData(TDefinitionType dataDefinition, TDataType dataField) { //TDataType oldValue = GetData(dataDefinition); // Console.WriteLine("{0}++++ {1}.SetData ({2})={3}=>{4}", DateTime.Now.ToString("HH:mm:ss fff"), this.GetType(), dataDefinition, oldValue, dataField); if (!InternalSetData(dataDefinition, dataField)) throw new StateEngineException("Unable to set data " + dataDefinition); } /// /// Gets data from the definition. /// /// /// public TDataType GetData(TDefinitionType dataDefinition) { TDataType result; if (InternalGetData(dataDefinition, out result)) { //Console.WriteLine("---- {0}.GetData ({1})={2}", this.GetType(), dataDefinition, result); return result; } else throw new StateEngineException("Unable to get data " + dataDefinition); } #endregion #region Internal Methods private bool InternalSetData(TDefinitionType dataDefinition, TDataType dataField) { if (dataDict.ContainsKey(dataDefinition)) { //Validate the type of the value that is already there. //It may // 1. Be of the exact same type as the data was defined // 2. It may be a descendent class to the defined. object initialValue = dataDefinitionDict[dataDefinition]; if (dataDefinitionType[dataDefinition].IsAssignableFrom(typeof(TDataType))) dataDict[dataDefinition] = dataField; else throw new InvalidCastException(string.Concat("Cannot set data with definition ", dataDefinition, " as type ", typeof(TDataType).Name, "; should be ", initialValue.GetType().Name)); return true; } else if (parentStateData != null) { return parentStateData.InternalSetData(dataDefinition, dataField); } else { return false; } } private bool InternalGetData(TDefinitionType dataDefinition, out TDataType dataField) { if (dataDict.ContainsKey(dataDefinition)) { object value = dataDict[dataDefinition]; Type type = dataDefinitionType[dataDefinition]; //It's ok to get data as long the data is of the requested type. if (type.Equals(typeof(TDataType))) { dataField = (TDataType)value; return true; } else { throw new InvalidCastException(string.Concat("Cannot get data with definition ", dataDefinition, " as type ", typeof(TDataType).Name, "; is ", value.GetType().Name)); } } else if (parentStateData != null) { return parentStateData.InternalGetData(dataDefinition, out dataField); } else { dataField = default(TDataType); return false; } } /// /// Used from the different generic data states to handle fetch the Composite state that contains the state data that this state should use. /// /// /// /// internal static IStateWithData GetParentCompositeStateWithStateData(State state) where TData : class { CompositeState compositeState = state.ParentState; while (compositeState != null) { IStateWithData stateWithData = compositeState as IStateWithData; if (stateWithData != null) { if (stateWithData.StateData is TData) { return stateWithData; } } //Search down the inheritance line. compositeState = compositeState.ParentState; } throw new StateEngineException("State with state data of type " + typeof(TData) + " is not contained in any matching composite state."); } #endregion /// /// Dispose /// /// protected virtual void Dispose(bool disposing) { } /// /// Dispose /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }