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);
}
}
}