• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Classes
  • Files
  • File List
  • File Members

src/StateMachine.cpp

00001 /*
00002  *   This file is part of the Standard Portable Library (SPL).
00003  *
00004  *   SPL is free software: you can redistribute it and/or modify
00005  *   it under the terms of the GNU General Public License as published by
00006  *   the Free Software Foundation, either version 3 of the License, or
00007  *   (at your option) any later version.
00008  *
00009  *   SPL is distributed in the hope that it will be useful,
00010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *   GNU General Public License for more details.
00013  *
00014  *   You should have received a copy of the GNU General Public License
00015  *   along with SPL.  If not, see <http://www.gnu.org/licenses/>.
00016  */
00017 #include <spl/io/File.h>
00018 #include <spl/math/StateMachine.h>
00019 #include <spl/xml/XmlElement.h>
00020 #include <spl/xml/XmlDocument.h>
00021 
00022 IStateEventListener::~IStateEventListener()
00023 {
00024 }
00025 
00026 TransitionActionResolver::TransitionActionResolver()
00027 : m_map()
00028 {
00029 }
00030 
00031 TransitionActionResolver::TransitionActionResolver(const TransitionActionResolver& resolver)
00032 : m_map(resolver.m_map)
00033 {
00034 }
00035 
00036 TransitionActionResolver::~TransitionActionResolver()
00037 {
00038 }
00039 
00040 #ifdef DEBUG
00041 void TransitionActionResolver::ValidateMem() const
00042 {
00043         m_map.ValidateMem();
00044 }
00045 
00046 void TransitionActionResolver::CheckMem() const
00047 {
00048         m_map.CheckMem();
00049 }
00050 #endif
00051 
00052 State::State
00053 (
00054         const char *name, 
00055         const char *enterActionName, 
00056         const char *leaveActionName, 
00057         IStateEventListener *enterAction, 
00058         IStateEventListener *leaveAction
00059 )
00060 :       m_name(name), 
00061         m_enterActionName(enterActionName), 
00062         m_leaveActionName(leaveActionName), 
00063         m_enterAction(enterAction),
00064         m_leaveAction(leaveAction),
00065         m_fromThisState(),
00066         m_transitionList()
00067 {
00068 }
00069 
00070 State::~State()
00071 {
00072         for ( int x = 0; x < m_transitionList.Count(); x++ )
00073         {
00074                 delete m_transitionList.ElementAt(x);
00075         }
00076         m_transitionList.Clear();       
00077 }
00078 
00079 Transition *State::GetTransition( const String& inputText ) 
00080 { 
00081         Transition *tran = m_fromThisState.Get( inputText );
00082         if ( NULL != tran )
00083         {
00084                 return tran;
00085         }
00086         // check for lamda default transition
00087         return m_fromThisState.Get(String("*"));
00088 }
00089 
00090 void State::FireOnLeave(const String& input, State *to)
00091 {
00092         if ( NULL != m_leaveAction )
00093         {
00094                 m_leaveAction->OnStateLeave(input, this, to);
00095         }
00096 }
00097 
00098 void State::FireOnEnter(const String& input, State *from)
00099 {
00100         if ( NULL != m_enterAction )
00101         {
00102                 m_enterAction->OnStateEnter(input, from, this);
00103         }
00104 }
00105 
00106 #ifdef DEBUG
00107 void State::ValidateMem() const
00108 {
00109         m_name.ValidateMem();
00110         m_enterActionName.ValidateMem();
00111         m_leaveActionName.ValidateMem();
00112         m_fromThisState.ValidateMem();
00113         m_transitionList.ValidateMem();
00114 }
00115 
00116 void State::CheckMem() const
00117 {
00118         m_name.CheckMem();
00119         m_enterActionName.CheckMem();
00120         m_leaveActionName.CheckMem();
00121         m_fromThisState.CheckMem();
00122         m_transitionList.CheckMem();
00123 }
00124 #endif
00125 
00126 Transition::Transition
00127 (
00128         const char *onInput, 
00129         State *from, 
00130         State *to, 
00131         const char *actionName, 
00132         IStateEventListener *transitionAction
00133 )
00134 :       m_onInput(onInput), 
00135         m_from(from), 
00136         m_to(to), 
00137         m_actionName(actionName), 
00138         m_transitionAction(transitionAction)
00139 {
00140 }
00141 
00142 Transition::~Transition()
00143 {
00144 }
00145 
00146 void Transition::FireOnTransition(const String& input)
00147 {
00148         if ( NULL != m_transitionAction )
00149         {
00150                 m_transitionAction->OnStateTransition(input, this);
00151         }
00152 }
00153 
00154 #ifdef DEBUG
00155 void Transition::ValidateMem() const
00156 {
00157         m_onInput.ValidateMem();
00158         m_actionName.ValidateMem();
00159 }
00160 
00161 void Transition::CheckMem() const
00162 {
00163         m_onInput.CheckMem();
00164         m_actionName.CheckMem();
00165 }
00166 #endif
00167 
00168 StateMachine::StateMachine()
00169 : m_resolver(), m_currentState(NULL), m_stateIdx(), m_states()
00170 {
00171 }
00172 
00173 StateMachine::~StateMachine()
00174 {
00175         for ( int x = 0; x < m_states.Count(); x++ )
00176         {
00177                 delete m_states.ElementAt(x);
00178         }
00179 }
00180 
00181 void StateMachine::SetState( const char *name )
00182 {
00183         State *state = m_stateIdx.Get( String(name) );
00184         if ( NULL == state )
00185         {
00186                 throw new InvalidArgumentException("State not found");
00187         }
00188         ASSERT_MEM( state, sizeof(State) );
00189         state->ValidateMem();
00190         m_currentState = state;
00191         m_currentState->FireOnEnter(String(""), NULL);
00192 }
00193 
00194 void StateMachine::DefineState( const char *name, const char *enterActionName, const char *leaveActionName )
00195 {
00196         IStateEventListener *ea = m_resolver.Get(enterActionName);
00197         if ( NULL == ea && enterActionName[0] != '\0' )
00198         {
00199                 throw new InvalidArgumentException("Enter action not found");
00200         }
00201         IStateEventListener *la = m_resolver.Get(leaveActionName);
00202         if ( NULL == la && leaveActionName[0] != '\0' )
00203         {
00204                 throw new InvalidArgumentException("Leave action not found");
00205         }
00206         String sname(name);
00207         if ( m_stateIdx.ContainsKey( sname ) )
00208         {
00209                 throw new InvalidArgumentException("State already exists -- state names must be unique");
00210         }
00211 
00212         State *state = new State(name, enterActionName, leaveActionName, ea, la);
00213         if ( NULL == state )
00214         {
00215                 throw OutOfMemoryException();
00216         }
00217         m_stateIdx.Set( sname, state );
00218         m_states.Add( state );
00219 }
00220 
00221 void StateMachine::DefineTransition
00222 ( 
00223         const char *onInput, 
00224         const char *from, 
00225         const char *to, 
00226         const char *actionName 
00227 )
00228 {
00229         IStateEventListener *la = m_resolver.Get(actionName);
00230         if ( NULL == la )
00231         {
00232                 throw new InvalidArgumentException("Action not found");
00233         }
00234         State *tstate = m_stateIdx.Get(to);
00235         if ( NULL == tstate )
00236         {
00237                 throw new InvalidArgumentException("To state not found");
00238         }
00239         State *fstate = m_stateIdx.Get( from );
00240         if ( NULL == fstate )
00241         {
00242                 throw new InvalidArgumentException("From state not found");
00243         }
00244         if ( fstate->ContainsTransition( onInput ) )
00245         {
00246                 throw new InvalidArgumentException("State already has a transition defined for this input");
00247         }
00248         fstate->AddTransitionFromThisState( new Transition(onInput, fstate, tstate, actionName, la) );
00249 }
00250 
00251 void StateMachine::ChangeState(const char *input)
00252 {
00253         String sinput(input);
00254         if ( NULL == m_currentState )
00255         {
00256                 throw new StateException("Current state is NULL, use SetState to set the start state");
00257         }
00258         ASSERT_MEM( m_currentState, sizeof(State) );
00259         m_currentState->ValidateMem();
00260         
00261         Transition *trans = m_currentState->GetTransition(sinput);
00262         if ( NULL == trans )
00263         {
00264                 throw new StateException("There is no transition for that input");
00265         }
00266         ASSERT_MEM( trans, sizeof(Transition) );
00267         trans->ValidateMem();
00268         ASSERT( trans->GetFromState() == m_currentState );
00269         ASSERT( trans->InputText().Equals(input) || trans->InputText().Equals("*") );
00270         
00271         m_currentState->FireOnLeave(sinput, trans->GetToState());
00272         trans->FireOnTransition(sinput);
00273         m_currentState = trans->GetToState();
00274         m_currentState->FireOnEnter(sinput, trans->GetFromState());
00275 }
00276 
00277 void StateMachine::Load( const char *filename )
00278 {
00279         if ( ! File::Exists(filename) )
00280         {
00281                 throw new IOException("File not found");
00282         }
00283         XmlDocumentPtr doc = XmlDocument::Parse(filename);
00284         XmlElementPtr statemachine = doc->FirstChildElement("statemachine");
00285         if ( statemachine.IsNull() )
00286         {
00287                 throw new StateException("Invalid XML file -- top-level '<statemachine>' tag not found.");
00288         }
00289         XmlElementPtr state = statemachine->FirstChildElement("state");
00290         while ( state.IsNotNull() )
00291         {
00292                 XmlAttributePtr name = state->Attribute("name");
00293                 if ( name.IsNull() )
00294                 {
00295                         throw new StateException("<state> must have a name attribute");
00296                 }
00297                 XmlAttributePtr enteraction = state->Attribute("enteraction");
00298                 XmlAttributePtr exitaction = state->Attribute("exitaction");
00299                 DefineState( name->Value()->GetChars(), (enteraction.IsNull()) ? NULL : enteraction->Value()->GetChars(), (exitaction.IsNull()) ? NULL : exitaction->Value()->GetChars() );
00300 
00301                 state = state->NextSiblingElement("state");
00302         }
00303 
00304         state = statemachine->FirstChildElement("state");
00305         while ( state.IsNotNull() )
00306         {
00307                 XmlAttributePtr name = state->Attribute("name");
00308 
00309                 XmlElementPtr transition = state->FirstChildElement("transition");
00310                 while ( transition.IsNotNull() )
00311                 {
00312                         XmlAttributePtr input = transition->Attribute("input");
00313                         XmlAttributePtr to = transition->Attribute("to");
00314                         if ( input.IsNull() || to.IsNull() )
00315                         {
00316                                 throw new StateException("<transition> must have 'input' and 'to' attributes");
00317                         }
00318                         XmlAttributePtr enteraction = transition->Attribute("action");
00319                         DefineTransition(input->Value()->GetChars(), name->Value()->GetChars(), to->Value()->GetChars(), (enteraction.IsNull()) ? NULL : enteraction->Value()->GetChars());
00320 
00321                         transition = transition->NextSiblingElement("transition");
00322                 }
00323 
00324                 state = state->NextSiblingElement("state");
00325         }
00326 }
00327 
00328 #ifdef DEBUG
00329 void StateMachine::ValidateMem() const
00330 {
00331         m_resolver.ValidateMem();
00332         m_states.ValidateMem();
00333         m_stateIdx.ValidateMem();
00334 }
00335 
00336 void StateMachine::CheckMem() const
00337 {
00338         m_resolver.CheckMem();
00339         m_states.CheckMem();
00340         m_stateIdx.CheckMem();
00341 }
00342 #endif