using System; using System.Collections.Generic; using System.Diagnostics; namespace Wayne.Lib.StateEngine.Generic { /// <summary> /// 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. /// </summary> public abstract class StateData : IDisposable { #region Fields private readonly StateData parentStateData; private readonly Dictionary<object, object> dataDict = new Dictionary<object, object>(); private readonly Dictionary<object, object> dataDefinitionDict = new Dictionary<object, object>(); private readonly Dictionary<object, Type> dataDefinitionType = new Dictionary<object, Type>(); #endregion #region Construction /// <summary> /// Constructor /// </summary> /// <param name="parentStateData">Reference to the parent composite state's state data object.</param> protected StateData(StateData parentStateData) { this.parentStateData = parentStateData; } /// <summary> /// Finalizer /// </summary> ~StateData() { Dispose(false); } #endregion #region Protected Methods /// <summary> /// Defines a parameter and specifies the type and initial value for it. /// </summary> /// <param name="dataDefinition"></param> /// <param name="initialValue"></param> protected void DefineData<TDefinitionType, TDataType>(TDefinitionType dataDefinition, TDataType initialValue) { if (!DataIsDefined(dataDefinition)) { dataDict[dataDefinition] = initialValue; dataDefinitionDict[dataDefinition] = initialValue; dataDefinitionType[dataDefinition] = typeof(TDataType); } } /// <summary> /// Checks if the data is defined already in a parent state data. /// </summary> /// <param name="dataDefinition"></param> /// <returns></returns> private bool DataIsDefined(object dataDefinition) { if (dataDict.ContainsKey(dataDefinition)) return true; else if (parentStateData != null) return parentStateData.DataIsDefined(dataDefinition); else return false; } /// <summary> /// Sets a new value to a field that is defined by dataDefinition. /// </summary> /// <param name="dataDefinition"></param> /// <param name="dataField"></param> protected void SetData<TDefinitionType, TDataType>(TDefinitionType dataDefinition, TDataType dataField) { //TDataType oldValue = GetData<TDefinitionType, TDataType>(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); } /// <summary> /// Gets data from the definition. /// </summary> /// <param name="dataDefinition"></param> /// <returns></returns> public TDataType GetData<TDefinitionType, TDataType>(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, TDataType>(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, TDataType>(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; } } /// <summary> /// Used from the different generic data states to handle fetch the Composite state that contains the state data that this state should use. /// </summary> /// <typeparam name="TData"></typeparam> /// <param name="state"></param> /// <returns></returns> internal static IStateWithData GetParentCompositeStateWithStateData<TData>(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 /// <summary> /// Dispose /// </summary> /// <param name="disposing"></param> protected virtual void Dispose(bool disposing) { } /// <summary> /// Dispose /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }