Source

symplehfsm / trunk / symplehfsm / symplehfsm.py

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
This module provides a simple but powerful way to define testable, hierarchical finite state machines. You should know how
a state machine works. This implementation provides following features:

* guard action
* entry and exit actions
* transition action
* external and local transitions (see: https://en.wikipedia.org/wiki/UML_state_machine#Local_versus_external_transitions)
* hierarchically nested states (see: https://en.wikipedia.org/wiki/UML_state_machine#Hierarchically_nested_states)
* testable
* clear defined events interface
* clear defined actions interface
* execution speed optimization (transitions are converted to lookup's in a dictionary, even for a hierarchical structure)
* memory efficient because state machine structure can be shared by all instances




References:

#. https://en.wikipedia.org/wiki/State_pattern.
#. https://en.wikipedia.org/wiki/Finite-state_machine.
#. https://en.wikipedia.org/wiki/UML_state_machine.





.. todo:: tutorial (explaining the abilities, entry/exit/actions/guard, different kinds of transitions)
.. todo:: set log level through event handling method??
.. todo:: debug mode should log what it does but respect the debug level (?)
.. todo:: move BaseState and Transition into Structure class (because they are kinda private classes) ??
"""

__version__ = "0.0.1.dev"
__author__ = "dr0iddr0id {at} gmail [dot] com (C) 2012"

import unittest
import logging


if __file__:
    LOG_FILENAME = __file__ + '.log'
else:
    LOG_FILENAME = __module__ + '.log'

logger = logging.getLogger("symplehfsm")    # pylint: disable=C0103
# loglevel = logging.DEBUG    # pylint: disable=C0103
loglevel = logging.INFO    # pylint: disable=C0103
logger.setLevel(loglevel)
if __debug__:
    _ch = logging.StreamHandler()   # pylint: disable=C0103
    _ch.setLevel(logging.DEBUG)
    logger.addHandler(_ch)
handler = logging.FileHandler(LOG_FILENAME)  # pylint: disable=C0103
handler.setLevel(loglevel)
logger.addHandler(handler)

# -----------------------------------------------------------------------------

class StateUnknownError(Exception):
    """
    Exception raised if the state is not known in the structure.
    """
    pass

# -----------------------------------------------------------------------------


class BaseState(object):
    """
    BaseState from which all hirarchical states should inherit.
    :Note: The state itself is 'stateless'.

    :Parameters:
        name : string
            name of this state
        parent : BaseState
            Reference to the parent state, for the root state use None
            (only one state has None as parent since there is only
            one root)

    """
    class InitialStateAlreadySetError(Exception):
        """Exception is raised if initial state is already set."""
        pass

    class InitialNotSetError(Exception):
        """Exception raised if the initial state is not set."""
        pass

    class InitialNotReplacedError(Exception):
        """Exception raised if the initial state is not replaced."""
        pass

    class ParentAlreadySetError(Exception):
        """Exception raised when a child has already a parent set"""
        pass

    class ReplacementStateIsNotChildError(Exception):
        """Exception raised if the replaced initial state is not a child."""
        pass

    class WrongParentError(Exception):
        """
        Exception raised if the set parent is not the same state
        containint it as child
        """
        pass

    def __init__(self, name=None, parent=None):
        self.parent = None
        self.initial = None
        self.children = []
        self.optimized = {}
        self.name = str(id(self))
        self.entry = None
        self.exit = None
        self.events = {}  # {event:trans}
        if parent:
            parent.add(self)
        if name:
            self.name = name

    def add(self, child, initial=False):
        """
        Adds another state as child to this state.

        :Parameters:
            child : BaseState
                the child state to add
            initial : bool
                defaults to False, if set, the child state is the
                initial state.
        """
        if child.parent is not None:
            raise self.ParentAlreadySetError("child state '{0}' has already a parent {1} when trying to add it to {2}".format(child, child.parent, self))
        if initial:
            if self.initial is not None:
                raise self.InitialStateAlreadySetError(\
                        str.format("initial already set to {1} for state {0}", self, self.initial))
            self.initial = child
        child.parent = self
        self.children.append(child)
        return child

    def remove(self, child, replace=None):
        """
        Removes a child state. If the removed child state was the initial
        state it has to be replaced.

        :Parameters:
            child : BaseState
                child state to be removed.
            replace : BaseState
                the new initial state if the removed one was the initial state.
        """
        if child in self.children:
            if replace is None:
                if self.initial == child:
                    raise self.InitialNotReplacedError("missing replacement since child {0} \
                                                    to be removed is initial state for {1}".format(self.initial, self))
            else:
                if not self.has_child(replace):
                    raise self.ReplacementStateIsNotChildError("replacement state {0} is not \
                                                    a child of this state {1}".format(replace, self))
                self.initial = replace
            child.parent = None
            self.children.remove(child)

    def has_child(self, child_state):
        """
        Checks if a state has a certain state as a child.

        :Parameters:
            child_state : BaseState
                child_state to check

        :returns:
            bool
        """
        parent = child_state.parent
        while parent:
            if parent is self:
                return True
            parent = parent.parent

        return False

    def is_child(self, parent_state):
        """
        Checks if this state is a child state of a parent state.

        :Parameters:
            parent_state : BaseState
                the parent state to check if this is its child state.

        :returns:
            bool
        """
        parent = self.parent
        while parent:
            if parent is parent_state:
                return True
            parent = parent.parent

        return False

    def check_consistency(self):
        """
        Checks the consistency of the state hierarchy.
        It checks mainly two things:

            - if the initial state is set for each state having a child or
                children, raises InitialNotSetError otherwise
            - if each child of a state has the parent attribute set to that
                state, raises WrongParentError otherwise

        ..todo: move check_consistency to Structure class?
        """
        if self.initial is None and len(self.children) > 0:
            raise self.InitialNotSetError(\
                    "state {0}: initial has to be set if a state has at least one child".format(self))
        for child in self.children:
            if child.parent != self:
                raise self.WrongParentError(\
                "parent {0} of a child {1} is set to another state, should be {2}".format(child.parent, child, self))
            child.check_consistency()

    def __str__(self):
        return str.format("<{0}[{1}]>", self.__class__.__name__, self.name)

    __repr__ = __str__

# -----------------------------------------------------------------------------

class Transition(object):
    """
    This class holds the data needed for a transition.

    Represents the transition between (composite) states (just the arrow
    in the state chart).
    The transition itself is 'stateless'.

    :Parameters:
        target_state : State
            The state this transition should change to.
        action : methodcaller
            This should be a methodcaller object or a function
            behaving like a methodcaller. Such a function would
            have following signature (return value is ignored)::

                def f(actions)

            A function behaving like a methodcaller looks like
            this::

                  f = lambda actions: actions.any_method_of_actions()

            :Note: only the function knows which function to call on the actions object.
        guard : methodcaller
            a methodaller of a function that behaves like a methodcaller
            returning a boolean, its signature is::

                guard(actions) -> bool

            If True is returned, then the transition will be followed,
            otherwise the transition will be blocked and event processing
            stops (no parent states are considered).
    """

    def __init__(self, target_state, action=None, guard=None, name=None):
        self.guard = guard
        self.target = target_state
        self.action = action
        self.name = str(id(self))
        if name:
            self.name = name

    def __str__(self):
        return str.format("<{0}[{1}]>", self.__class__.__name__, self.name)

# -----------------------------------------------------------------------------

class Structure(object):
    """
    This is the class holding the state machine structure, e.g. the number
    of states and their relationship (hierarchy) and its transitions in between them.

    Ths is also the code that is shared by many instances of the same statemachine.

    :Parameters:
        name : string
            Optional name for this instance of this class.
    """

    class RootAlreadySetOrParentMissingError(Exception):
        """
        Exception raised when the parent is missing or the root has already
        been set.
        """
        pass

    class ParentUnkownError(Exception):
        """Exception raised when the parent is unkown."""
        pass

    class EventAlreadyDefinedError(Exception):
        """Exception raised when the event is already defined for that state"""
        pass

    class StateIdentifierAlreadyUsed(Exception):
        """Exception raised when another state has the same state identifier."""
        pass

    def __init__(self, name=None):
        self.states = {}  # {id:State}
        self.root = None
        self.is_optimized = False
        self.name = str(id(self))
        if name:
            self.name = name

    def __str__(self):
        return str.format("<{0}[{1}]>", self.__class__.__name__, self.name)

    # #                          name,    parent, initial,            entry,        exit
    # sm_structure.add_state("s0",          None, False,  methodcaller("entry_s0"), context.methodcaller("exit_s0"))
    def add_state(self, state, parent, initial, entry_action=None, exit_action=None):
        """
        Add a new node representing a state to the structure.

        :Parameters:
            state : State identifier
                A hashable identifier for that state (name, id, etc.). Has to be unique.
            parent : State identifier
                A hashable identifier of the state that is set as parent.
                The only one state will have set its parent to None, its the root state.
            initial : bool
                Only one of the children of a state can have this set to true, its the
                state that is used to descent to a leaf node of the structure.
            entry_action : methodcaller
                The methodcaller or a function behaving like a methodcaller. That calls
                the entry function on the actions object for that state. Optional, defaults to: None
            exit_action : methodcaller
                The methodcaller or a function behaving like a methodcaller. That calls
                the exit function on the actions object for that state. Optional, defaults to: None
        """
        if parent and not parent in self.states:
            raise self.ParentUnkownError("parent of {0} is unkown".format(str(state)))

        internal_state = BaseState(name=str(state))
        internal_state.events = {}  # {event:trans}
        # internal_state.parent = parent
        internal_state.entry = entry_action
        internal_state.exit = exit_action
        if parent:
            self.states[parent].add(internal_state, initial)
        else:
            if self.root:
                raise self.RootAlreadySetOrParentMissingError("root is already set to '{0}' \
                                                        or parent of '{1}' is missing".format(self.root, state))
            self.root = state
        if state in self.states:
            raise self.StateIdentifierAlreadyUsed(str(state))
        self.states[state] = internal_state

    # #                   handler, event, target,           action,     guard
    # sm_structure.add_trans("s1",   "a",   "s1", methodcaller("action_a"), methodcaller("guard_a"))
    def add_trans(self, state, event, target, action=None, guard=None, name=None):
        """
        Add a transition between two states for a certain event.

        :Parameters:
            state : State identifier
                A hashable identifier for that state (name, id, etc.).
            event : event identifiert
                A hashable event identifier. The same identifiert has to be used
                when calling handle_event on the state machine.
            target : state identifier
                The state this transition will lead too.
            action : methodcaller
                The transition action. Optional, default: None
            guard : methodcaller
                The guard method. Should return a boolean.
                If the return value is True, then the transition is carried out. Otherwise the
                event processing stops and nothing changes.
        """
        if not state in self.states:
            raise StateUnknownError("Unknown state: " + str(state))
        if target is not None and not target in self.states:
            raise StateUnknownError("target not set or unkown")
        internal_state = self.states[state]
        if event in internal_state.events:
            raise self.EventAlreadyDefinedError("state '{0}' has event '{1}' already set".format(\
                                                                                    str(state), str(event)))
        internal_state.events[event] = Transition(target, action, guard, str(name))

    def do_optimize(self):
        """
        Optimizes the event processing of the state machine. Call this method before you pass
        the structure to the constructor to create a state machine instance.

        :note: Dont change the structure after calling this method (no add/remove states nor transitions).

        :note: Only the first call will actually optimize the structure, all following calls will do noting.
        """
        # don't optimize twice
        if self.is_optimized:
            return

        # collect all possible events
        events = set()
        [events.update(list(x.events.keys())) for x in list(self.states.values())]

        if __debug__:
            logger.info('>>> ' + str(events))
        # apply alle events to all leaf states to get optimization
        leafs = [x for x in list(self.states.values()) if not x.children]
        if __debug__:
            logger.info('>>> ' + str(leafs))

        for leaf in leafs:
            for event in events:
                if __debug__:
                    logger.info("optimizing state '{0}' for event '{1}".format(str(leaf), str(event)))
                if not event in leaf.optimized:
                    guard, methodcalls, target_node = self._get_methodcallers(str(self) + " (optimizing)", event, leaf)
                    leaf.optimized[event] = (guard, methodcalls, target_node)
        self.is_optimized = True

    def _get_methodcallers(self, state_machine, event, current_state):
        """
        Computes what 'actions' a transition has to execute for a given state and event.

        :Parameters:
            state_machine : SympleDictHFSM
                The state machine to use, its for log purposes only.
            event : eventidentifier
                The event identifier to know which event to execute.
            current_state : state identifier
                The state to apply the event (the next state is defined by the
                transitions for that event if defined).

        :Returns:
            tuple containing: (guard, methodcalls, next_state) where
                guard is a methodcaller returning bool
                methodcalls is a list of methodcallers of entry/exit/transition actions in the correct order
                next_state is the the state the state machine has to be after executing that event
                    (note: its not a state identifier, its an instance of BaseState)

        """
        methodcalls = []
        guard = None
        # find the event handling state in the hierarchy
        if __debug__:
            logger.info(str.format("{0}: handling event '{1}' (current state: '{2}')", state_machine, event, current_state))
        source_node = current_state
        nodes = []
        transition = None
        while transition is None:
            if event in source_node.events:
                transition = source_node.events[event]
            if transition is None:
                nodes.append(source_node)
                if source_node.parent:
                    source_node = source_node.parent
                else:
                    break

        if transition is None or transition is False:
            if __debug__:
                logger.info(str.format("{0}: no event handling state nor transition found, no state change", state_machine))
            # event not handled
            return guard, methodcalls, current_state
        # transition.guard is here because the exits of the nodes
        # should only be run if the guard returns true

        if __debug__:
            logger.debug(str.format("{0}: event handling state found: {1}", state_machine, source_node))
            if transition and not issubclass(transition.__class__, Transition):
                raise TypeError("transition returned by a state is not a subclass of " + str(Transition))

        if transition.guard:
            guard = transition.guard
        else:
            if __debug__:
                logger.debug(str.format("{0}: transition has no guard function", state_machine))

        if __debug__:
            logger.debug(str.format("{0}: executing transition", state_machine, transition))

        # exits
        for node in nodes:
            if node.exit:
                if __debug__:
                    logger.debug(str.format("{0}: calling exit on state: {1}", state_machine, node))
                methodcalls.append(node.exit)

        # transition
        # go up the hirarchy as needed for the transition, find shared parent state
        if __debug__:
            logger.debug(str.format("{0}: finding parent states of transition...", state_machine))
        target_node = source_node
        if transition.target is not None:
            target_node = self.states[transition.target]
            if __debug__:
                logger.debug(str.format("{0}: source '{1}', target '{2}'", state_machine, source_node, target_node))
            if source_node != target_node:
                if __debug__:
                    logger.debug(str.format("{0}: case source != target", state_machine))
                while not target_node.is_child(source_node):
                    if source_node == target_node:
                        if __debug__:
                            logger.debug(str.format("{0}: found target == source", state_machine))
                        break  # this break is needed
                    else:
                        if source_node.exit:
                            if __debug__:
                                logger.debug(str.format("{0}: calling exit on state: {1}", state_machine, source_node))
                            methodcalls.append(source_node.exit)
                        source_node = source_node.parent
            else:
                if source_node.exit:
                    if __debug__:
                        logger.debug(str.format("{0}: case source == target", state_machine))
                        logger.debug(str.format("{0}: calling exit on state: {1}", state_machine, source_node))
                    methodcalls.append(source_node.exit)
                source_node = source_node.parent

        if transition.action:
            if __debug__:
                logger.info(str.format("{0}: calling action of transition: {1}", state_machine, transition))
            methodcalls.append(transition.action)
            if __debug__:
                logger.debug("{0}: source '{1}', target '{2}', transition action done".format(\
                                                                        state_machine, source_node, target_node))

        # find child node and go down the hirarchy until target_node is found
        if __debug__:
            logger.debug(str.format("{0}: finding child states for transition...", state_machine))
        if transition.target is not None:
            if source_node != target_node:
                while source_node != target_node:
                    for child in source_node.children:
                        if child == target_node:
                            if target_node.entry:
                                if __debug__:
                                    logger.info(str.format("{0}: calling entry on: {1}", state_machine, target_node))
                                methodcalls.append(target_node.entry)
                            source_node = target_node
                            break
                        elif target_node.is_child(child):
                            if child.entry:
                                if __debug__:
                                    logger.info(str.format("{0}: calling entry on: {1}", state_machine, target_node))
                                methodcalls.append(child.entry)
                            source_node = child
                            break
            else:
                if __debug__:
                    logger.debug(str.format("{0}: find child node: source_node == target_node", state_machine))

        # initial entries
        if __debug__:
            logger.debug(str.format("{0}: following initial states...", state_machine))
        if target_node.initial:
            target_node = target_node.initial
            while target_node.initial:
                if target_node.entry:
                    if __debug__:
                        logger.debug(str.format("{0}: calling entry on state: {1}", state_machine, target_node))
                    methodcalls.append(target_node.entry)
                target_node = target_node.initial
            # last node should be entered too
            if target_node.entry:
                if __debug__:
                    logger.debug(str.format("{0}: calling entry on state: {1}", state_machine, target_node))
                methodcalls.append(target_node.entry)

        if __debug__:
            logger.info(str.format("{0}: changed state from {1} to {2} using transition {3}", state_machine, current_state, target_node, transition))

        return guard, methodcalls, target_node

# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------

class SympleDictHFSM(object):
    """
    .. todo:: should transition.action be able to return something to the caller?

    Base state machine logic. It implements the state transition logic.

    :Parameters:
        structure : Structure
            The state machine structure of states and transitions
        actions : Actions
            The object implementing the actions interface to be used by the state machine.
        name : string
            Optional, default: None. This name will be used for logging and printing.

    """

    class ReentrantEventException(Exception):
        """
        Exception raised if an event is already processing.
        """
        pass

    class NotInitializedException(Exception):
        """
        Exception raised if it is attemped to process an event before
        init has been called.
        """
        pass

    def __init__(self, structure, actions, name=None):
        self.actions = actions
        self._structure = structure
        self._current_state = None
        self._currently_handling_event = False
        self.name = str(id(self))
        if name:
            self.name = name
        self.handle_event = self._handle_event_not_inititalized

    def __str__(self):
        return str.format("<{0}[{1}]>", self.__class__.__name__, self.name)

    def _get_current_state(self):
        """
        Returns the current state.

        :returns: the current state of the state machine or None
        """
        return self._current_state
    current_state = property(_get_current_state, doc="""Current state pointer,\
                    could be None if the state machine is not initialized""")

    def set_state(self, new_state):
        """
        Set the state directly as the current state without calling
        any entry or exit or any other events on any state. Don't use it unless you need to (like initializing).
        Use with caution. Raises a 'ReentrantEventException' if it is currently processing an event.

        :Parameters:
            new_state : state
                State to which current state will point afterwards.
        """
        if new_state not in list(self._structure.states.values()):
            raise StateUnknownError(str(new_state))
        self._current_state = new_state

    def init(self, use_optimization=True):
        """
        Initialize the state machine. It descents along the 'initial' attribute of the states and sets the current_state accordingly.

        :Parameters:
            use_optimization : boolean
                Default: True. If set to False the event handling method will always compute the entire path
                through the structure. Otherwise if set to True and the structure has been optmized, then the
                cached transition information is used.
        """
        node = self._structure.states[self._structure.root]
        node.check_consistency()

        while True:
            if __debug__:
                logger.debug(str.format("{0}: INIT, calling entry on {1}", self, node))
            if node.entry:
                node.entry(self.actions)
            if not node.initial:
                break
            node = node.initial

        if __debug__:
            logger.debug(str.format("{0}: INIT done, current state: {1}", self, node))
        self._current_state = node

        # set up the right event handling method bo be used
        if use_optimization and self._structure.is_optimized:
            self.handle_event = self._handle_event_optimized
            if __debug__:
                logger.info("{0}: INIT using optmized structure".format(self))
        else:
            self.handle_event = self._handle_event_normal
            if __debug__:
                logger.info("{0}: INIT not using optmized structure".format(self))

    def exit(self):
        """
        Exits the state machine. Starting from the current_state it calls exit along the parent attribute on each state until
        the root state is exited.
        """
        node = self.current_state
        while node:
            if __debug__:
                logger.debug(str.format("{0}: EXIT, calling exit on {1}", self, node))
            if node.exit:
                node.exit(self.actions)
            node = node.parent
        self._current_state = None

    def _handle_event_not_inititalized(self, *args):
        """
        The event handling method that gets called when the state machine is not initialized yet.
        """
        raise self.NotInitializedException("Call init befor any event processing!")

    def _handle_event_optimized(self, event):
        """
        The event handling method used when the structure is optimized.

        .. todo:: how to remove those checks? use queue to make those check unneeded??
        """
        if self._currently_handling_event:
            raise self.ReentrantEventException("multi threading or calling a event function from within an actions \
                                                                                during event handling is not supported")
        self._currently_handling_event = True

        guard, methodcalls, target_node = self._current_state.optimized[event]

        if guard:
            if guard(self.actions) is False:
                if __debug__:
                    logger.info(str.format("{0}: guard of transition returned 'False', not changing state", self))
                self._currently_handling_event = False
                return
            if __debug__:
                logger.info(str.format("{0}: guard of transition returned 'True'", self))

        for methodcaller in methodcalls:
            methodcaller(self.actions)

        # set the new current state
        self._current_state = target_node

        self._currently_handling_event = False

    def _handle_event_normal(self, event):
        """
        The event handling method if the structure is not optimized, computes the way through the hierarchy.
        Handles the event and does a state change if needed. Raises a 'ReentrantEventException' if it is currently processing an event.

        :Parameters:
            event_func : operator.methodcaller
                A methodcaller instance pointed to the function that should be called on the state.
                For example if the method 'a' should be called on each state, then this should be 'event_func = operator.methodcaller('a', context)'
            context : context
                the context of the state machine, where certain methods and data is accesible (like the actions interface).

        """
        if self._current_state is None:
            raise self.NotInitializedException("Call init befor any event processing!")
        if self._currently_handling_event:
            raise self.ReentrantEventException("multi threading or calling a event function from within an actions\
                                                                                during event handling is not supported")
        self._currently_handling_event = True

        guard, methodcalls, target_node = self._structure._get_methodcallers(self, event, self._current_state)

        if guard:
            if guard(self.actions) is False:
                if __debug__:
                    logger.info(str.format("{0}: guard of transition returned 'False', not changing state", self))
                self._currently_handling_event = False
                return
            if __debug__:
                logger.info(str.format("{0}: guard of transition returned 'True'", self))

        for methodcaller in methodcalls:
            methodcaller(self.actions)

        # set the new current state
        self._current_state = target_node

        self._currently_handling_event = False


    def handle_event(self, event):
        """
        Handles the event and does a state change if needed. Raises a 'ReentrantEventException' if it is currently processing an event.

        :Parameters:
            event_func : operator.methodcaller
                A methodcaller instance pointed to the function that should be called on the state.
                For example if the method 'a' should be called on each state, then this should be 'event_func = operator.methodcaller('a', context)'
            context : context
                the context of the state machine, where certain methods and data is accesible (like the actions interface).

        """
        # its here for the documentation, its set in the code directly, used as a function pointer
        pass



# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------


class BaseHFSMTests(unittest.TestCase):
    """
    Base TestCase that already defines test code for testing state machines
    build using an events and action interface
    (see: http://accu.org/index.php/journals/1548)
    """

    class RecordingActions(object):
        """
        This is a class that records the names of the functions called on it.
        Instead of writing a TestActions class, that records which action was
        activated, this class can be used. Just use the method names of the
        Action interface to compare the actually called method with the
        expected method in the tests.

        :Instancevariable:
            captured_actions : list
                List of captured method names that where called.
            args : list
                List of tuples '(args, kwargs)' in the order the action methods
                where called.
                For each action method call there is a tuple inserted.
                If no arguments are passed then a empty tuple is
                inserted, e.g. '( ( ,), ( ,) )'

        """

        def __init__(self):
            self.captured_actions = []
            self._name = None  # for internal use
            self.args = []

        def __getattr__(self, name):
            self._name = name
            return self._nop

        def _nop(self, *args, **kwargs):
            """
            This is the method that actually gets called instead of
            the real actions method. It will record the call.
            """
            self.args.append((args, kwargs))
            self.captured_actions.append(self._name)

    class TestVector(object):
        """
        A TestVector is basically the data container needed to test one
        transition.

        :Parameters:
            title : string
                Description of this TestVector
            starting_state : State
                the state from which this transition starts
            event_func : Func
                the function handling the event
            expected_state : State
                the state that should be the current_state after
                the transition
            expected_actions : list
                list of expected actions to be compared with the
                captured actions

        """

        def __init__(self,
                    title,
                    starting_state,
                    event_func,
                    expected_state,
                    expected_actions):
            self.title = title
            self.starting_state = starting_state
            self.event_func = event_func
            self.expected_state = expected_state
            self.expected_actions = expected_actions

    def prove_one_transition(self,
                            state_machine,
                            resulting_actions,
                            test_vector):
        """
        Test one transition.

        :Parameters:
            state_machine : StateMachine
                the instance of the state machine to use
            resulting_actions : Actions
                instance of the class implementing the Actions that
                captures the actions
                needs to have an attribute 'captured_actions' which is a list
                of the captured actions
            test_vector : TestVector
                the TestVector to test
        """

        state_machine.set_state(test_vector.starting_state)

        # clear the results of changing to the starting state
        resulting_actions.captured_actions = []

        test_vector.event_func()

        if len(test_vector.expected_actions) != \
                                    len(resulting_actions.captured_actions):
            self.fail("Not same number of expected and captured actions!\
                                \n expected: {0} \n \
                                captured: {1}".format( \
                                ", ".join(test_vector.expected_actions), \
                                ", ".join(resulting_actions.captured_actions)))

        for idx, expected_action in enumerate(test_vector.expected_actions):
            action = resulting_actions.captured_actions[idx]
            if action != expected_action:
                self.fail(str.format("captured action does not match with \
                        expected action! \n expected: {0} \n captured: {1}", \
                            ", ".join(test_vector.expected_actions), \
                            ", ".join(resulting_actions.captured_actions)))

        msg = "state machine not in expected state after transition, current: \
                    {0} expected: {1}".format(\
                    state_machine.current_state, test_vector.expected_state)
        self.assertTrue(test_vector.expected_state == \
                                            state_machine.current_state, msg)

        msg = "state machine ! in expected state after transition, current: \
                    {0} expected: {1}".format(\
                    state_machine.current_state, test_vector.expected_state)
        self.assertTrue(test_vector.expected_state is \
                                            state_machine.current_state, msg)

    def prove_transition_sequence(self,
                                    title,
                                    starting_state,
                                    event_funcs,
                                    expected_state,
                                    expected_actions,
                                    state_machine,
                                    resulting_actions):
        """
        Test a sequence of transitions by passing in a sequence of event and checking the actions.

        :Parameters:
            title : string
                Description of this test
            starting_state : State
                the state from which this transition starts
            event_funcs : Func
                list of event functions to call
            expected_state : State
                the state that should be the current_state after the transition
            expected_actions : list
                list of expected actions to be compared with the captured actions
            state_machine : SympleHFSM
                the statemachine to test, an instance of SympleHFSM (or inheritet class)
            resulting_actions : Actions
                the actions used for the statemachine and for this test, has to have an attribute 'captured_actions'
        """
        state_machine.set_state(starting_state)

        # clear the results of changing to the starting state
        resulting_actions.captured_actions = []

        for event_func in event_funcs:
            event_func()

        if len(expected_actions) != len(resulting_actions.captured_actions):
            self.fail(str.format("Not same number of expected and captured actions! \n \
                                    expected: {0} \n \
                                    captured: {1}", \
                                    ", ".join(expected_actions), \
                                    ", ".join(resulting_actions.captured_actions)))

        for idx, expected_action in enumerate(expected_actions):
            action = resulting_actions.captured_actions[idx]
            if action != expected_action:
                self.fail(str.format("captured action does not match with expected action! \n \
                                        expected: {0} \n \
                                        captured: {1}", \
                                    ", ".join(expected_actions), \
                                    ", ".join(resulting_actions.captured_actions)))

        msg = "state machine not in expected state after transition, current: {0} expected: {1}".format(\
                                                                    state_machine.current_state, expected_state)
        self.assertTrue(expected_state == state_machine.current_state, msg)

        msg = "state machine ! in expected state after transition, current: {0} expected: {1}".format(\
                                                                    state_machine.current_state, expected_state)
        self.assertTrue(expected_state is state_machine.current_state, msg)

# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------