using System; using System.Collections.Generic; using System.Linq; namespace Wayne.Lib.StateEngine { public class StateLookupEntry { #region Fields public string NextStateFactoryName { get; internal set; } public HistoryType HistoryType { get; private set; } #endregion #region Construction public StateLookupEntry(string nextStateFactoryName, HistoryType historyType) { NextStateFactoryName = nextStateFactoryName; HistoryType = historyType; } #endregion } /// /// StateTransitionLookup is used by the State machine to find the state to /// change to when a transition occurs. It uses a dataset that contains the actual data. /// public class StateTransitionLookup { #region Fields private readonly StateTypeContainer stateTypeContainer; #endregion #region Construction /// /// Constructor /// public StateTransitionLookup(StateTypeContainer stateTypeContainer) { LookupTable = new Dictionary>(); this.stateTypeContainer = stateTypeContainer; } public Dictionary> LookupTable { get; private set; } #endregion #region Method: GetNextState /// /// Performs a Lookup for the next state when in the source state, and /// are going to perform the specified transition /// /// /// The state the state machine is in when performing transition /// /// /// The requested transition. /// /// /// Returns the next state that should be entered based on the transition. /// public StateEntry GetNextState(string sourceStateFactoryName, Wayne.Lib.StateEngine.Transition transition) { if (transition == null) return null; StateLookupEntry stateLookupEntry; //Try look up with the current state name if (TryLookup(sourceStateFactoryName, transition.Type, out stateLookupEntry)) { return new StateEntry(transition, stateLookupEntry.NextStateFactoryName, stateLookupEntry.HistoryType); } //If not matching with current state name, try lookup recursively with AnyState if (sourceStateFactoryName != AnyState.FactoryName) //Unless we are already in recursion. { return GetNextState(AnyState.FactoryName, transition); } return null; } public bool TryLookup(string sourceStateFactoryName, object transitionType, out StateLookupEntry stateLookupEntry) { stateLookupEntry = null; Dictionary lookup1; if (LookupTable.TryGetValue(sourceStateFactoryName, out lookup1)) { return lookup1.TryGetValue(transitionType, out stateLookupEntry); } return false; } #endregion #region Method: AddTransition /// /// Internal add transition. /// /// /// /// /// /// private void InternalAddTransition(string sourceStateFactoryName, object transitionType, string nextStateType, HistoryType historyType, bool replace) { if (historyType == HistoryType.Explicit) throw new StateEngineException("Cannot configure a transition to have a explicit history type. It is only used internally when working with explicit transitions"); var stateLookup = GetTransitionsForState(sourceStateFactoryName); var exists = stateLookup.ContainsKey(transitionType); if (exists && !replace) { throw new ArgumentException(string.Format("The State-Transition combination \"{0}-{1}\" has already been defined.", sourceStateFactoryName, Transition.GetTransitionName(transitionType))); } if (!exists && replace) { throw new ArgumentException(string.Format("The State-Transition combination \"{0}-{1}\" is not defined.", sourceStateFactoryName, Transition.GetTransitionName(transitionType))); } StateLookupEntry lookupEntry = new StateLookupEntry(nextStateType, historyType); stateLookup[transitionType] = lookupEntry; } /// /// Returns a transition dictionary for the specified state. /// /// /// public Dictionary GetTransitionsForState(string sourceStateFactoryName) { Dictionary result; if (!LookupTable.TryGetValue(sourceStateFactoryName, out result)) { LookupTable.Add(sourceStateFactoryName, result = new Dictionary()); } return result; } /// /// Adds a transition from state of class TFromState to the class TToState, when the transitiontype /// is inserted. /// /// Class type of the source state. /// Class type of target state. /// Transition type. public void AddTransition(object transitionType) where TFromState : State where TToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); InternalAddTransition(typeof(TFromState).FullName, transitionType, typeof(TToState).FullName, HistoryType.None, false); } /// /// Adds a transition from state of class fromStateFactoryName to the class toStateFactoryName, when the transitiontype /// is inserted. /// /// String containig the factory name for the fromState. /// String containig the factory name for the toState. /// Transition type. public void AddTransition(string fromStateFactoryName, string toStateFactoryName, object transitionType) { InternalAddTransition(fromStateFactoryName, transitionType, toStateFactoryName, HistoryType.None, false); } /// /// Adds a transition from state of class TFromState to the class TToState, when the transitiontype /// is inserted. /// /// Class type of the source state. /// Class type of target state. /// Transition type. /// public void AddTransition(object transitionType, HistoryType historyType) where TFromState : State where TToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); InternalAddTransition(typeof(TFromState).FullName, transitionType, typeof(TToState).FullName, historyType, false); } #endregion #region Method: ReplaceTransition (Specifying both From and To state) /// /// Replaces the existing transition from a state and the given TransitionType, to another state. /// /// Class type of the source state. /// Class type of new target state. /// Transition type. public void ReplaceTransition(object transitionType) where TFromOldState : State where TToNewState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); InternalAddTransition(typeof(TFromOldState).FullName, transitionType, typeof(TToNewState).FullName, HistoryType.None, true); } /// /// Replaces the existing transition from a state and the given TransitionType, to another state. /// /// String containig the factory name for the fromState. /// String containig the factory name for the new toState. /// Transition type. public void ReplaceTransition(string fromOldStateFactoryName, string toNewStateFactoryName, object transitionType) { InternalAddTransition(fromOldStateFactoryName, transitionType, toNewStateFactoryName, HistoryType.None, true); } /// /// Replaces the existing transition from a state and the given TransitionType, to another state. /// /// Class type of the source state. /// Class type of new target state. /// Transition type. /// public void ReplaceTransition(object transitionType, HistoryType historyType) where TFromOldState : State where TToNewState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); InternalAddTransition(typeof(TFromOldState).FullName, transitionType, typeof(TToNewState).FullName, historyType, true); } #endregion #region Method: ReplaceTransitions (Specifying only To state) /// /// Replaces all the existing transitions to the given OldToState to the NewToState. /// /// Class type of old target state. /// Class type of new target state. public void ReplaceTransitions() where TOldToState : State where TNewToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); ReplaceTransitions(typeof(TOldToState).FullName, typeof(TNewToState).FullName); } /// /// Replaces all the existing transitions to the given OldToState to the NewToState. /// /// String containig the factory name for the old ToState. /// String containig the factory name for the new ToState. public void ReplaceTransitions(string oldToStateFactoryName, string newToStateFactoryName) { LookupTable.Values .SelectMany(x => x.Values) .Where(x => x.NextStateFactoryName == oldToStateFactoryName) .ForEach(x => x.NextStateFactoryName = newToStateFactoryName); } #endregion #region Method: IntersectTransition /// /// Puts a state in-between two states that has a transition between them. /// /// /// /// /// public void IntersectTransition(object transitionType) where TFromState : State where TIntersectingState : State where TOldToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); stateTypeContainer.Register(); IntersectTransition(transitionType, transitionType); } /// /// Puts a state in-between two states that has a transition between them. /// /// /// /// /// /// public void IntersectTransition(object fromTransitionType, object toTransitionType) where TFromState : State where TIntersectingState : State where TOldToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); stateTypeContainer.Register(); StateLookupEntry dummy; if (!TryLookup(typeof(TFromState).FullName, fromTransitionType, out dummy)) { throw new StateEngineException(string.Format("No previous Transition {0}, from {1} to {2} exists to intersect.", Transition.GetTransitionName(fromTransitionType), typeof(TFromState).FullName, typeof(TOldToState).FullName)); } //If the Transition TFromState to TOldState with toTransitionType doesn't exist throw an Error. ReplaceTransition(fromTransitionType); AddTransition(toTransitionType); } #endregion #region Method: ReplaceState /// /// Replaces all the existing transitions to the given OldToState to the NewToState. /// Also adds the transitions from the OldToState to also from the NewToState. /// /// Class type of old target state. /// Class type of new target state. public void ReplaceState() where TOldToState : State where TNewToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); ReplaceState(typeof(TOldToState).FullName, typeof(TNewToState).FullName, false); } /// /// Replaces all the existing transitions to the given OldToState to the NewToState. /// Also adds the transitions from the OldToState to also from the NewToState. /// /// Default=false. Should all existing transitions *From* this state be removed? /// Class type of old target state. /// Class type of new target state. public void ReplaceState(bool removeAllTransitionsFromState) where TOldToState : State where TNewToState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); ReplaceState(typeof(TOldToState).FullName, typeof(TNewToState).FullName, removeAllTransitionsFromState); } /// /// Replaces all the existing transitions to the given OldToState to the NewToState. /// Also adds the transitions from the OldToState to also from the NewToState. /// /// String containig the factory name for the old ToState. /// String containig the factory name for the new ToState. public void ReplaceState(string oldToStateFactoryName, string newToStateFactoryName) { ReplaceState(oldToStateFactoryName, newToStateFactoryName, false); } /// /// Replaces all the existing transitions to the given OldToState to the NewToState. /// Also adds the transitions from the OldToState to also from the NewToState. /// Note: 'OverrideState' also exists, to be used e.g. when replacing state that transists to itself /// /// The factory name for the old ToState. /// The factory name for the new ToState. /// Default=false. Should all existing transitions *From* this state be removed? public void ReplaceState(string oldToStateFactoryName, string newToStateFactoryName, bool removeAllTransitionsFromState) { var transitionsForState = GetTransitionsForState(oldToStateFactoryName); //Get a list of the self-ref transitions var selfRefTransitions = transitionsForState .Where(x => x.Value.NextStateFactoryName == oldToStateFactoryName) .ToArray(); //Update all transitions going FROM the old state var transitions = transitionsForState; LookupTable.Remove(oldToStateFactoryName); if (!removeAllTransitionsFromState) { LookupTable.Add(newToStateFactoryName, transitions); } //Update all transitions going TO the old state. LookupTable.Values .SelectMany(x => x.Values) .Where(x => x.NextStateFactoryName == oldToStateFactoryName) .ForEach(x => x.NextStateFactoryName = newToStateFactoryName); //Warning : //Add super-weird old broken self links for backwards compatibility selfRefTransitions.ForEach(t => { transitionsForState.Remove(t.Key); InternalAddTransition(oldToStateFactoryName, t.Key, newToStateFactoryName, t.Value.HistoryType, false); }); } #endregion #region Method: OverrideState /// /// Makes an override of a state. /// This could be used when you have made an inheritance of a state and want to replace the old one. /// /// The state to override. /// The overrider. public void OverrideState() where TOldState : State where TNewState : State { stateTypeContainer.Register(); stateTypeContainer.Register(); string oldToStateFactoryName = typeof(TOldState).FullName; string newToStateFactoryName = typeof(TNewState).FullName; OverrideState(oldToStateFactoryName, newToStateFactoryName); } /// /// Makes an override of a state. /// This could be used when you have made an inheritance of a state and want to replace the old one. /// /// /// public void OverrideState(string oldToStateFactoryName, string newToStateFactoryName) { //Update all transitions going FROM the old state var transitions = GetTransitionsForState(oldToStateFactoryName); LookupTable.Remove(oldToStateFactoryName); LookupTable.Add(newToStateFactoryName, transitions); //Update all transitions going TO the old state. LookupTable.Values .SelectMany(x => x.Values) .Where(x => x.NextStateFactoryName == oldToStateFactoryName) .ForEach(x => x.NextStateFactoryName = newToStateFactoryName); } #endregion #region Methods: GetLists /// /// Returns a list of state factory names that is in the lookup table. /// /// Should the AnyStates be included? public string[] GetStateNameList(bool includeAnyStates) { var stateNames = LookupTable.Keys .Concat(LookupTable .Values .SelectMany(dict => dict.Values.Select(entry => entry.NextStateFactoryName))) .Distinct(); if (!includeAnyStates) { stateNames = stateNames.Where(x => x != AnyState.FactoryName); } return stateNames.ToArray(); } #endregion } }