MINT-statemachine 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/CHANGES +135 -0
  2. data/LICENSE +16 -0
  3. data/MINT-statemachine.gemspec +27 -0
  4. data/README.rdoc +69 -0
  5. data/Rakefile +88 -0
  6. data/TODO +2 -0
  7. data/lib/statemachine.rb +26 -0
  8. data/lib/statemachine/action_invokation.rb +83 -0
  9. data/lib/statemachine/builder.rb +383 -0
  10. data/lib/statemachine/generate/dot_graph.rb +1 -0
  11. data/lib/statemachine/generate/dot_graph/dot_graph_statemachine.rb +127 -0
  12. data/lib/statemachine/generate/java.rb +1 -0
  13. data/lib/statemachine/generate/java/java_statemachine.rb +265 -0
  14. data/lib/statemachine/generate/src_builder.rb +48 -0
  15. data/lib/statemachine/generate/util.rb +50 -0
  16. data/lib/statemachine/parallelstate.rb +196 -0
  17. data/lib/statemachine/state.rb +102 -0
  18. data/lib/statemachine/statemachine.rb +279 -0
  19. data/lib/statemachine/stub_context.rb +26 -0
  20. data/lib/statemachine/superstate.rb +53 -0
  21. data/lib/statemachine/transition.rb +76 -0
  22. data/lib/statemachine/version.rb +17 -0
  23. data/spec/action_invokation_spec.rb +101 -0
  24. data/spec/builder_spec.rb +243 -0
  25. data/spec/default_transition_spec.rb +111 -0
  26. data/spec/generate/dot_graph/dot_graph_stagemachine_spec.rb +27 -0
  27. data/spec/generate/java/java_statemachine_spec.rb +349 -0
  28. data/spec/history_spec.rb +107 -0
  29. data/spec/noodle.rb +23 -0
  30. data/spec/sm_action_parameterization_spec.rb +99 -0
  31. data/spec/sm_activation_spec.rb +116 -0
  32. data/spec/sm_entry_exit_actions_spec.rb +99 -0
  33. data/spec/sm_odds_n_ends_spec.rb +67 -0
  34. data/spec/sm_parallel_state_spec.rb +207 -0
  35. data/spec/sm_simple_spec.rb +26 -0
  36. data/spec/sm_super_state_spec.rb +55 -0
  37. data/spec/sm_turnstile_spec.rb +76 -0
  38. data/spec/spec_helper.rb +121 -0
  39. data/spec/transition_spec.rb +107 -0
  40. metadata +115 -0
@@ -0,0 +1,76 @@
1
+ module Statemachine
2
+
3
+ class Transition #:nodoc:
4
+
5
+ attr_reader :origin_id, :event, :action
6
+ attr_accessor :destination_id, :cond
7
+
8
+ def initialize(origin_id, destination_id, event, action, cond)
9
+ @origin_id = origin_id
10
+ @destination_id = destination_id
11
+ @event = event
12
+ @action = action
13
+ @cond = cond
14
+ end
15
+
16
+ def invoke(origin, statemachine, args)
17
+ destination = statemachine.get_state(@destination_id)
18
+ exits, entries = exits_and_entries(origin, destination)
19
+ exits.each { |exited_state| exited_state.exit(args) }
20
+ messenger = origin.statemachine.messenger
21
+ message_queue = origin.statemachine.message_queue
22
+
23
+ if @action # changed this if statement to return if action fails
24
+ if not origin.statemachine.invoke_action(@action, args, "transition action from #{origin} invoked by '#{@event}' event", messenger, message_queue)
25
+ raise StatemachineException.new("Transition to state #{destination.id} failed because action for event #{@event} return false.")
26
+ return
27
+ end
28
+ end
29
+
30
+ # origin.statemachine.invoke_action(@action, args, "transition action from #{origin} invoked by '#{@event}' event", messenger, message_queue) if @action
31
+
32
+ terminal_state = entries.last
33
+ entries.each { |entered_state| entered_state.activate(terminal_state.id) if entered_state.is_parallel }
34
+
35
+ terminal_state.activate if terminal_state and not terminal_state.is_parallel
36
+
37
+ entries.each { |entered_state| entered_state.enter(args) }
38
+ end
39
+
40
+ def exits_and_entries(origin, destination)
41
+ # return [], [] if origin == destination
42
+ exits = []
43
+ entries = exits_and_entries_helper(exits, origin, destination)
44
+ return exits, entries.reverse
45
+ end
46
+
47
+ def to_s
48
+ return "#{@origin_id} ---#{@event}---> #{@destination_id} : #{action} if #{cond}"
49
+ end
50
+
51
+ private
52
+
53
+ def exits_and_entries_helper(exits, exit_state, destination)
54
+ entries = entries_to_destination(exit_state, destination)
55
+ return entries if entries
56
+ return [] if exit_state == nil
57
+
58
+ exits << exit_state
59
+ exits_and_entries_helper(exits, exit_state.superstate, destination)
60
+ end
61
+
62
+ def entries_to_destination(exit_state, destination)
63
+ return nil if destination.nil?
64
+ entries = []
65
+ state = destination.resolve_startstate
66
+ while state
67
+ entries << state
68
+ return entries if exit_state == state.superstate
69
+ state = state.superstate
70
+ end
71
+ return nil
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,17 @@
1
+ module Statemachine
2
+ module VERSION #:nodoc:
3
+ unless defined? MAJOR
4
+ MAJOR = 1
5
+ MINOR = 2
6
+ TINY = 2
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ TAG = "REL_" + [MAJOR, MINOR, TINY].join('_')
10
+
11
+ NAME = "MINT-Statemachine"
12
+ URL = "http://www.multi-access.de/open-source-software/third-party-software-extensions/"
13
+
14
+ DESCRIPTION = "#{NAME}-#{STRING} - Statemachine Library for Ruby based on statemachine from http://slagyr.github.com/statemachine\n#{URL}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,101 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ #require 'statemachine'
3
+ #require 'lib/statemachine/action_invokation.rb'
4
+ require "noodle"
5
+
6
+ describe "Action Invokation" do
7
+
8
+ before(:each) do
9
+ @noodle = Noodle.new
10
+ end
11
+
12
+ it "Proc actions" do
13
+ sm = Statemachine.build do |smb|
14
+ smb.trans :cold, :fire, :hot, Proc.new { @cooked = true }
15
+ end
16
+
17
+ sm.context = @noodle
18
+ sm.fire
19
+
20
+ @noodle.cooked.should equal(true)
21
+ end
22
+
23
+ it "Symbol actions" do
24
+ sm = Statemachine.build do |smb|
25
+ smb.trans :cold, :fire, :hot, :cook
26
+ smb.trans :hot, :mold, :changed, :transform
27
+ end
28
+
29
+ sm.context = @noodle
30
+ sm.fire
31
+
32
+ @noodle.cooked.should equal(true)
33
+
34
+ sm.mold "capellini"
35
+
36
+ @noodle.shape.should eql("capellini")
37
+ end
38
+
39
+ it "String actions" do
40
+ sm = Statemachine.build do |smb|
41
+ smb.trans :cold, :fire, :hot, "@shape = 'fettucini'; @cooked = true"
42
+ end
43
+ sm.context = @noodle
44
+
45
+ sm.fire
46
+ @noodle.shape.should eql("fettucini")
47
+ @noodle.cooked.should equal(true)
48
+ end
49
+
50
+ it "Multiple Proc actions" do
51
+ sm = Statemachine.build do |smb|
52
+ smb.trans :cold, :fire, :hot, [Proc.new { @cooked = true }, Proc.new { @tasty = true }]
53
+ end
54
+
55
+ sm.context = @noodle
56
+ sm.fire
57
+
58
+ @noodle.cooked.should equal(true)
59
+ @noodle.tasty.should equal(true)
60
+ end
61
+
62
+ it "Multiple Symbol actions" do
63
+ sm = Statemachine.build do |smb|
64
+ smb.trans :cold, :fire, :hot, [:cook, :good]
65
+ end
66
+
67
+ sm.context = @noodle
68
+ sm.fire
69
+
70
+ @noodle.cooked.should equal(true)
71
+ @noodle.tasty.should equal(true)
72
+ end
73
+
74
+ it "Multiple actions" do
75
+ sm = Statemachine.build do |smb|
76
+ smb.trans :cold, :fire, :hot, [:cook, Proc.new { @tasty = true }, "@shape = 'fettucini'"]
77
+ end
78
+
79
+ sm.context = @noodle
80
+ sm.fire
81
+
82
+ @noodle.cooked.should equal(true)
83
+ @noodle.tasty.should equal(true)
84
+ @noodle.shape.should eql("fettucini")
85
+ end
86
+
87
+ it "No actions" do
88
+ sm = Statemachine.build do |smb|
89
+ smb.trans :cold, :fire, :hot
90
+ end
91
+
92
+ sm.context = @noodle
93
+ sm.fire
94
+
95
+ @noodle.cooked.should == false
96
+ @noodle.tasty.should == false
97
+ @noodle.shape.should =="farfalla"
98
+ end
99
+
100
+
101
+ end
@@ -0,0 +1,243 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Builder" do
4
+
5
+ before(:each) do
6
+ @log = []
7
+ end
8
+
9
+ def check_switch(sm)
10
+ sm.state.should equal(:off)
11
+
12
+ sm.toggle
13
+ @log[0].should eql("toggle on")
14
+ sm.state.should equal(:on)
15
+
16
+ sm.toggle
17
+ @log[1].should eql("toggle off")
18
+ sm.state.should equal(:off)
19
+ end
20
+
21
+ it "Building a the switch, relaxed" do
22
+ sm = Statemachine.build do
23
+ trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
24
+ trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
25
+ end
26
+ sm.context = self
27
+
28
+ check_switch sm
29
+ end
30
+
31
+ it "Building a the switch, strict" do
32
+ sm = Statemachine.build do
33
+ state(:off) { |s| s.event :toggle, :on, Proc.new { @log << "toggle on" } }
34
+ state(:on) { |s| s.event :toggle, :off, Proc.new { @log << "toggle off" } }
35
+ end
36
+ sm.context = self
37
+
38
+ check_switch sm
39
+ end
40
+
41
+ it "Adding a superstate to the switch" do
42
+ the_context = self
43
+ sm = Statemachine.build do
44
+ superstate :operation do
45
+ event :admin, :testing, lambda { @log << "testing" }
46
+ trans :off, :toggle, :on, lambda { @log << "toggle on" }
47
+ trans :on, :toggle, :off, lambda { @log << "toggle off" }
48
+ startstate :on
49
+ end
50
+ trans :testing, :resume, :operation, lambda { @log << "resuming" }
51
+ startstate :off
52
+ context the_context
53
+ end
54
+
55
+ sm.state.should equal(:off)
56
+ sm.toggle
57
+ sm.admin
58
+ sm.state.should equal(:testing)
59
+ sm.resume
60
+ sm.state.should equal(:on)
61
+ @log.join(",").should eql("toggle on,testing,resuming")
62
+ end
63
+
64
+ it "entry exit actions" do
65
+ the_context = self
66
+ sm = Statemachine.build do
67
+ state :off do
68
+ on_entry Proc.new { @log << "enter off" }
69
+ event :toggle, :on, lambda { @log << "toggle on" }
70
+ on_exit Proc.new { @log << "exit off" }
71
+ end
72
+ trans :on, :toggle, :off, lambda { @log << "toggle off" }
73
+ context the_context
74
+ end
75
+
76
+ sm.toggle
77
+ sm.state.should equal(:on)
78
+ sm.toggle
79
+ sm.state.should equal(:off)
80
+
81
+ @log.join(",").should eql("enter off,exit off,toggle on,toggle off,enter off")
82
+ end
83
+
84
+ it "History state" do
85
+ the_context = self
86
+ sm = Statemachine.build do
87
+ superstate :operation do
88
+ event :admin, :testing, lambda { @log << "testing" }
89
+ state :off do |off|
90
+ on_entry Proc.new { @log << "enter off" }
91
+ event :toggle, :on, lambda { @log << "toggle on" }
92
+ end
93
+ trans :on, :toggle, :off, lambda { @log << "toggle off" }
94
+ startstate :on
95
+ end
96
+ trans :testing, :resume, :operation_H, lambda { @log << "resuming" }
97
+ startstate :off
98
+ context the_context
99
+ end
100
+
101
+ sm.admin
102
+ sm.resume
103
+ sm.state.should equal(:off)
104
+
105
+ @log.join(",").should eql("enter off,testing,resuming,enter off")
106
+ end
107
+
108
+ it "entry and exit action created from superstate builder" do
109
+ the_context = self
110
+ sm = Statemachine.build do
111
+ trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
112
+ on_entry_of :off, Proc.new { @log << "entering off" }
113
+ trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
114
+ on_exit_of :on, Proc.new { @log << "exiting on" }
115
+ context the_context
116
+ end
117
+
118
+ sm.toggle
119
+ sm.toggle
120
+
121
+ @log.join(",").should eql("entering off,toggle on,exiting on,toggle off,entering off")
122
+
123
+ end
124
+
125
+ it "superstate as startstate" do
126
+
127
+ lambda do
128
+ sm = Statemachine.build do
129
+ superstate :mario_bros do
130
+ trans :luigi, :bother, :mario
131
+ end
132
+ end
133
+
134
+ sm.state.should equal(:luigi)
135
+ end.should_not raise_error(Exception)
136
+ end
137
+
138
+ it "setting the start state before any other states declared" do
139
+
140
+ sm = Statemachine.build do
141
+ startstate :right
142
+ trans :left, :push, :middle
143
+ trans :middle, :push, :right
144
+ trans :right, :pull, :middle
145
+ end
146
+
147
+ sm.state.should equal(:right)
148
+ sm.pull
149
+ sm.state.should equal(:middle)
150
+ end
151
+
152
+ it "setting start state which is in a super state" do
153
+ sm = Statemachine.build do
154
+ startstate :right
155
+ superstate :table do
156
+ event :tilt, :floor
157
+ trans :left, :push, :middle
158
+ trans :middle, :push, :right
159
+ trans :right, :pull, :middle
160
+ end
161
+ state :floor
162
+ end
163
+
164
+ sm.state.should equal(:right)
165
+ sm.pull
166
+ sm.state.should equal(:middle)
167
+ sm.push
168
+ sm.state.should equal(:right)
169
+ sm.tilt
170
+ sm.state.should equal(:floor)
171
+ end
172
+
173
+ it "can set context" do
174
+ widget = Object.new
175
+ sm = Statemachine.build do
176
+ context widget
177
+ end
178
+
179
+ sm.context.should be(widget)
180
+ end
181
+
182
+ it "statemachine will be set on context if possible" do
183
+ class Widget
184
+ attr_accessor :statemachine
185
+ end
186
+ widget = Widget.new
187
+
188
+ sm = Statemachine.build do
189
+ context widget
190
+ end
191
+
192
+ sm.context.should be(widget)
193
+ widget.statemachine.should be(sm)
194
+ end
195
+
196
+ it "should have an on_event" do
197
+ sm = Statemachine.build do
198
+ startstate :start
199
+ state :start do
200
+ on_event :go, :transition_to => :new_state
201
+ end
202
+ end
203
+ sm.go
204
+ sm.state.should == :new_state
205
+ end
206
+
207
+ it "should trigger actions using on_event" do
208
+ sm = Statemachine.build do
209
+ startstate :start
210
+ state :start do
211
+ on_event :go, :transition_to => :new_state, :and_perform => :action
212
+ end
213
+ end
214
+ object = mock("context")
215
+ object.stub(:action) { true }
216
+ sm.context = object
217
+ object.should_receive(:action)
218
+
219
+ sm.go
220
+ end
221
+
222
+ it "should have a transition_from" do
223
+ sm = Statemachine.build do
224
+ transition_from :start, :on_event => :go, :transition_to => :new_state
225
+ end
226
+
227
+ sm.go
228
+ sm.state.should == :new_state
229
+ end
230
+
231
+ it "should trigger actions on transition_from" do
232
+ sm = Statemachine.build do
233
+ transition_from :start, :on_event => :go, :transition_to => :new_state, :and_perform => :action
234
+ end
235
+ object = mock("context")
236
+ object.stub(:action) { true }
237
+ sm.context = object
238
+ object.should_receive(:action)
239
+
240
+ sm.go
241
+ end
242
+ end
243
+
@@ -0,0 +1,111 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Default Transition" do
4
+
5
+ before(:each) do
6
+ @sm = Statemachine.build do
7
+ trans :default_state, :start, :test_state
8
+
9
+ state :test_state do
10
+ default :default_state
11
+ end
12
+ end
13
+ end
14
+
15
+ it "the default transition is set" do
16
+ test_state = @sm.get_state(:test_state)
17
+ test_state.default_transition.should_not be(nil)
18
+ test_state.transition_for(:fake_event).should_not be(nil)
19
+ end
20
+
21
+ it "Should go to the default_state with any event" do
22
+ @sm.start
23
+ @sm.fake_event
24
+
25
+ @sm.state.should eql(:default_state)
26
+ end
27
+
28
+ it "default transition can have actions" do
29
+ me = self
30
+ @sm = Statemachine.build do
31
+ trans :default_state, :start, :test_state
32
+
33
+ state :test_state do
34
+ default :default_state, :hi
35
+ end
36
+ context me
37
+ end
38
+
39
+ @sm.start
40
+ @sm.blah
41
+
42
+ @sm.state.should eql(:default_state)
43
+ @hi.should eql(true)
44
+ end
45
+
46
+ def hi
47
+ @hi = true
48
+ end
49
+
50
+ it "superstate supports the default" do
51
+ @sm = Statemachine.build do
52
+ superstate :test_superstate do
53
+ default :default_state
54
+
55
+ state :start_state
56
+ state :default_state
57
+ end
58
+
59
+ startstate :start_state
60
+ end
61
+
62
+ @sm.blah
63
+ @sm.state.should eql(:default_state)
64
+ end
65
+
66
+ it "superstate transitions do not go to the default state" do
67
+ @sm = Statemachine.build do
68
+ superstate :test_superstate do
69
+ event :not_default, :not_default_state
70
+
71
+ state :start_state do
72
+ default :default_state
73
+ end
74
+
75
+ state :default_state
76
+ end
77
+
78
+ startstate :start_state
79
+ end
80
+
81
+ @sm.state = :start_state
82
+ @sm.not_default
83
+ @sm.state.should eql(:not_default_state)
84
+ end
85
+
86
+ it "should use not use superstate's default before using it's own default" do
87
+ @sm = Statemachine.build do
88
+ superstate :super do
89
+ default :super_default
90
+ state :base do
91
+ default :base_default
92
+ end
93
+ end
94
+ state :super_default
95
+ state :base_default
96
+ startstate :base
97
+ end
98
+
99
+ @sm.blah
100
+ @sm.state.should eql(:base_default)
101
+ end
102
+
103
+ it "should be marshalable" do
104
+ dump = Marshal.dump(@sm)
105
+ loaded = Marshal.load(dump)
106
+ loaded.state.should eql(:default_state)
107
+ end
108
+
109
+
110
+
111
+ end