MINT-statemachine 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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