MINT-statemachine 1.2.3 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,5 +1,27 @@
1
1
  = Statemachine Changelog
2
2
 
3
+ == Version 1.3.0
4
+
5
+ * (Sebastian) fixed bug that prevented exit call on parallel state exit
6
+ * (Sebastian) refactorings to support existing parallel states without calling events twice
7
+ * (Sebastian) if entering a parallel state, spontaneous initial transitions were not executed
8
+ * (Sebastian) introduced workaround to retrieve correct activation callback within parallel state machines
9
+ * (Jessica) fixed bug that prevented spontaneous transitions from initial state within superstate
10
+ * (Jessica) added test using transition to self to recheck spontaneous transition's condition.
11
+ * (Jessica) transitions now stored as array
12
+ * (Jessica) correct abstract states handling working. All tests working
13
+ * (Jessica) fixed bug that prevented in parallel state machine setups the correct publishing of all new atomic and abstract states
14
+ * (Sebastian) added option to temporarily disable activation callback for reset
15
+ * (Sebastian) several minor bug fixes
16
+ * (Sebastian) changed activation callback processing so it can be used fo publishing state updates with redis
17
+ * (Jessica) Added tests for on_entry, on_exit and transitions for parallel states.
18
+ * (Sebastian) fixed optional parameter handling
19
+ * (Sebastian) allow dynamic parameter size
20
+ * (Jessica) No more In() hack. Use is_in? instead
21
+ * (Jessica) Support for spontaneous transitions
22
+ * (Sebastian) abstract_states now also includes parallel state name
23
+ * (Jessica) added treatment for ifs
24
+
3
25
  == Version 1.2.3
4
26
 
5
27
  * Added Gemfile for bundler
data/LICENSE CHANGED
@@ -1,5 +1,5 @@
1
- Copyright (C) 2010,2011 Sebastian Feuerstack, Jessica Colnago
2
- Copyright (C) 2006-2010 Micah Martin
1
+ Copyright (C) 2010-2012 Sebastian Feuerstack, Jessica Colnago
2
+ Copyright (C) 2006-2010 Micah Martin
3
3
 
4
4
  This library is free software; you can redistribute it and/or
5
5
  modify it under the terms of the GNU Lesser General Public
@@ -1,20 +1,20 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Gem::Specification.new do |s|
4
- s.name = %q{MINT-statemachine}
5
- s.version = "1.2.3"
4
+ s.name = "MINT-statemachine"
5
+ s.version = "1.3.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
- s.authors = [%q{Sebastian Feuerstack}]
9
- s.date = %q{2011-06-30}
10
- s.description = %q{The MINT Statemachine is a ruby library for building Finite State Machines, based on the Statemachine gem by Micah Martin.}
11
- s.email = %q{Sebastian@Feuerstack.org}
12
- s.files = [%q{Rakefile}, %q{README.rdoc}, %q{CHANGES}, %q{TODO}, %q{LICENSE}, %q{lib/statemachine/generate/java/java_statemachine.rb}, %q{lib/statemachine/generate/src_builder.rb}, %q{lib/statemachine/generate/java.rb}, %q{lib/statemachine/generate/dot_graph.rb}, %q{lib/statemachine/generate/util.rb}, %q{lib/statemachine/generate/dot_graph/dot_graph_statemachine.rb}, %q{lib/statemachine/transition.rb}, %q{lib/statemachine/superstate.rb}, %q{lib/statemachine/version.rb}, %q{lib/statemachine/statemachine.rb}, %q{lib/statemachine/stub_context.rb}, %q{lib/statemachine/state.rb}, %q{lib/statemachine/parallelstate.rb}, %q{lib/statemachine/action_invokation.rb}, %q{lib/statemachine/builder.rb}, %q{lib/statemachine.rb}, %q{spec/sm_turnstile_spec.rb}, %q{spec/generate/java/java_statemachine_spec.rb}, %q{spec/generate/dot_graph/dot_graph_stagemachine_spec.rb}, %q{spec/sm_odds_n_ends_spec.rb}, %q{spec/noodle.rb}, %q{spec/sm_entry_exit_actions_spec.rb}, %q{spec/default_transition_spec.rb}, %q{spec/action_invokation_spec.rb}, %q{spec/sm_parallel_state_spec.rb}, %q{spec/builder_spec.rb}, %q{spec/sm_activation_spec.rb}, %q{spec/sm_super_state_spec.rb}, %q{spec/transition_spec.rb}, %q{spec/spec_helper.rb}, %q{spec/sm_simple_spec.rb}, %q{spec/sm_action_parameterization_spec.rb}, %q{spec/history_spec.rb}]
13
- s.homepage = %q{http://www.multi-access.de}
14
- s.require_paths = [%q{lib}]
15
- s.rubygems_version = %q{1.8.5}
16
- s.summary = %q{MINT-Statemachine-1.2.3 - Statemachine Library for Ruby based on statemachine from http://slagyr.github.com/statemachine http://www.multi-access.de/open-source-software/third-party-software-extensions/}
17
- s.test_files = [%q{spec/sm_turnstile_spec.rb}, %q{spec/sm_odds_n_ends_spec.rb}, %q{spec/sm_entry_exit_actions_spec.rb}, %q{spec/default_transition_spec.rb}, %q{spec/action_invokation_spec.rb}, %q{spec/sm_parallel_state_spec.rb}, %q{spec/builder_spec.rb}, %q{spec/sm_activation_spec.rb}, %q{spec/sm_super_state_spec.rb}, %q{spec/transition_spec.rb}, %q{spec/sm_simple_spec.rb}, %q{spec/sm_action_parameterization_spec.rb}, %q{spec/history_spec.rb}]
8
+ s.authors = ["Sebastian Feuerstack"]
9
+ s.date = "2012-11-20"
10
+ s.description = "The MINT Statemachine is a ruby library for building Finite State Machines, based on the Statemachine gem by Micah Martin."
11
+ s.email = "Sebastian@Feuerstack.org"
12
+ s.files = ["TODO", "Gemfile.lock", "LICENSE", "CHANGES", "Rakefile", "Gemfile", "README.rdoc", "MINT-statemachine.gemspec", "lib/statemachine.rb", "lib/statemachine/builder.rb", "lib/statemachine/statemachine.rb", "lib/statemachine/generate/util.rb", "lib/statemachine/generate/dot_graph/dot_graph_statemachine.rb", "lib/statemachine/generate/java/java_statemachine.rb", "lib/statemachine/generate/dot_graph.rb", "lib/statemachine/generate/src_builder.rb", "lib/statemachine/generate/java.rb", "lib/statemachine/transition.rb", "lib/statemachine/version.rb", "lib/statemachine/parallelstate.rb", "lib/statemachine/action_invokation.rb", "lib/statemachine/superstate.rb", "lib/statemachine/state.rb", "lib/statemachine/stub_context.rb", "spec/default_transition_spec.rb", "spec/spec_helper.rb", "spec/sm_super_state_spec.rb", "spec/sm_action_parameterization_spec.rb", "spec/generate/dot_graph/dot_graph_stagemachine_spec.rb", "spec/generate/java/java_statemachine_spec.rb", "spec/sm_entry_exit_actions_spec.rb", "spec/sm_simple_spec.rb", "spec/sm_parallel_state_spec.rb", "spec/transition_spec.rb", "spec/noodle.rb", "spec/sm_activation_spec.rb", "spec/builder_spec.rb", "spec/action_invokation_spec.rb", "spec/sm_turnstile_spec.rb", "spec/history_spec.rb", "spec/sm_odds_n_ends_spec.rb"]
13
+ s.homepage = "http://www.multi-access.de"
14
+ s.require_paths = ["lib"]
15
+ s.rubygems_version = "1.8.15"
16
+ s.summary = "MINT-Statemachine-1.3.0 - Statemachine Library for Ruby based on statemachine from http://slagyr.github.com/statemachine http://www.multi-access.de/open-source-software/third-party-software-extensions/"
17
+ s.test_files = ["spec/default_transition_spec.rb", "spec/sm_super_state_spec.rb", "spec/sm_action_parameterization_spec.rb", "spec/sm_entry_exit_actions_spec.rb", "spec/sm_simple_spec.rb", "spec/sm_parallel_state_spec.rb", "spec/transition_spec.rb", "spec/sm_activation_spec.rb", "spec/builder_spec.rb", "spec/action_invokation_spec.rb", "spec/sm_turnstile_spec.rb", "spec/history_spec.rb", "spec/sm_odds_n_ends_spec.rb"]
18
18
 
19
19
  if s.respond_to? :specification_version then
20
20
  s.specification_version = 3
data/README.rdoc CHANGED
@@ -51,7 +51,7 @@ http://www.multi-access.de
51
51
 
52
52
  == License
53
53
 
54
- Copyright (C) 2010,2011 Sebastian Feuerstack, Jessica Colnago
54
+ Copyright (C) 2010-2012 Sebastian Feuerstack, Jessica Colnago
55
55
  Copyright (C) 2006-2010 Micah Martin
56
56
 
57
57
  This library is free software; you can redistribute it and/or
@@ -20,13 +20,40 @@ module Statemachine
20
20
  result = send(a[1],a[2])
21
21
  elsif a[0] == 'invoke'
22
22
  result = invoke_method(a[1],args, message)
23
+ elsif a[0] == 'script'
24
+ result = invoke_string(a[1])
25
+ result = true if result == nil
26
+ elsif a[0] == "if"
27
+ result = invoke_string(a[1])
28
+ if result
29
+ result = invoke_action(a[2], [], message, messenger, message_queue)
30
+ return result
31
+ else
32
+ result = true
33
+ end
34
+ elsif a[0] == "elseif"
35
+ result = invoke_string(a[1])
36
+ if result
37
+ result = invoke_action(a[2], [], message, messenger, message_queue)
38
+ return result
39
+ else
40
+ result = true
41
+ end
42
+ elsif a[0] == "else"
43
+ result = a[1]
44
+ if result
45
+ result = invoke_action(a[2], [], message, messenger, message_queue)
46
+ return result
47
+ else
48
+ result = true
49
+ end
23
50
  end
24
51
  else
25
52
  log("#{a}")
26
53
  result = invoke_string(a) if not messenger
27
54
  end
28
55
  return false if result == false
29
- }
56
+ }
30
57
  result
31
58
  end
32
59
 
@@ -66,13 +93,33 @@ module Statemachine
66
93
  end
67
94
  end
68
95
 
96
+ #def params_for_block(block, args, message)
97
+ # arity = block.arity
98
+ # required_params = arity < 0 ? arity.abs - 1 : arity
99
+ #
100
+ # raise StatemachineException.new("Insufficient parameters. (#{message})") if required_params > args.length
101
+ #
102
+ # arity < 0 ? args : args[0...arity]
103
+ #end
104
+
105
+ # extended parameter handling that allows to define procs with variable parameter size and also
106
+ # supports cutting the arguments to match a fixed set of parameters
107
+
69
108
  def params_for_block(block, args, message)
70
- arity = block.arity
71
- required_params = arity < 0 ? arity.abs - 1 : arity
109
+ i = block.parameters
110
+
111
+ required_params = block.parameters.select {|x| x[0].eql? :req or x[0].eql? :opt}.length
112
+
113
+ includes_optional = block.parameters.select {|x| x[0].eql? :rest}.length > 0
72
114
 
73
115
  raise StatemachineException.new("Insufficient parameters. (#{message})") if required_params > args.length
74
116
 
75
- arity < 0 ? args : args[0...arity]
117
+ # in case we have no optional parameters but more args than parameters, vut the args to match the parameters
118
+ if args.length>required_params and includes_optional == false
119
+ args[0...required_params]
120
+ else
121
+ args
122
+ end
76
123
  end
77
124
 
78
125
  end
@@ -265,7 +265,7 @@ module Statemachine
265
265
  s = statemachine.get_state(id)
266
266
  if (s)
267
267
  statemachine.remove_state(@subject)
268
- s.transitions.each {|k,v|
268
+ s.transitions.each {|v|
269
269
  @subject.add(v)
270
270
  }
271
271
  end
@@ -344,6 +344,23 @@ module Statemachine
344
344
  statemachine.add_state(@subject)
345
345
  #puts "added #{@subject.inspect}"
346
346
  end
347
+
348
+ def on_entry(entry_action)
349
+ @subject.entry_action = entry_action
350
+ end
351
+
352
+ def on_exit(exit_action)
353
+ @subject.exit_action = exit_action
354
+ end
355
+
356
+ def event(event, destination_id, action = nil, cond = true)
357
+ @subject.add(Transition.new(@subject.id, destination_id, event, action, cond))
358
+ end
359
+
360
+ def trans(origin_id, event, destination_id, action = nil, cond = true)
361
+ origin = acquire_state_in(origin_id, @subject)
362
+ origin.add(Transition.new(origin_id, destination_id, event, action, cond))
363
+ end
347
364
  end
348
365
 
349
366
  # Created by Statemachine.build as the root context for building the statemachine.
@@ -41,7 +41,7 @@ module Statemachine
41
41
  @nodes = []
42
42
  @transitions = []
43
43
  @sm.states.values.each { |state|
44
- state.transitions.values.each { |transition|
44
+ state.transitions.each { |transition|
45
45
  @nodes << transition.origin_id
46
46
  @nodes << transition.destination_id
47
47
  @transitions << transition
@@ -72,7 +72,7 @@ module Statemachine
72
72
 
73
73
  add_graph_header(builder, state.id)
74
74
 
75
- state.transitions.values.each do |transition|
75
+ state.transitions.each do |transition|
76
76
  add_transition(builder, transition)
77
77
  end
78
78
 
@@ -46,7 +46,7 @@ module Statemachine
46
46
  events = []
47
47
  actions = []
48
48
  @sm.states.values.each do |state|
49
- state.transitions.values.each do |transition|
49
+ state.transitions.each do |transition|
50
50
  events << transition.event
51
51
  add_action(actions, transition.action)
52
52
  end
@@ -175,9 +175,9 @@ module Statemachine
175
175
  src << "{" << endl
176
176
  src.indent!
177
177
  add_one_liner(src, nil, "#{state_name}State", "#{@classname} statemachine", "super(statemachine)")
178
- state.transitions.keys.sort.each do |event_id|
179
- transition = state.transitions[event_id]
180
- add_state_event_handler(transition, src)
178
+ trans_aux = state.transitions
179
+ trans_aux.sort_by!{|t| t.event}.each do |t|
180
+ add_state_event_handler(t, src)
181
181
  end
182
182
  src.undent!
183
183
  src << "}" << endl
@@ -1,39 +1,36 @@
1
1
  module Statemachine
2
2
 
3
- class Parallelstate< Superstate
4
-
5
- attr_accessor :parallel_statemachines, :id
3
+ class Parallelstate < Superstate
4
+
5
+ attr_accessor :parallel_statemachines, :id, :entry_action, :exit_action
6
6
  attr_reader :startstate_ids
7
7
 
8
8
  def initialize(id, superstate, statemachine)
9
9
  super(id, superstate, statemachine)
10
10
  @parallel_statemachines=[]
11
11
  @startstate_ids=[]
12
+ @transitions = []
13
+ @spontaneous_transitions = []
12
14
  end
13
15
 
14
- # def startstate_id= id
15
- # if @parallel_statemachines.size>0
16
- # @startstate_ids[@parallel_statemachines.size-1]= id
17
- # end
18
- # end
19
- #
20
- # def startstate_id
21
- # if (@parallel_statemachines.size>0 and @parallel_statemachines.size==@startstate_ids.size)
22
- # return true
23
- # end
24
- # nil
25
- # end
16
+ def add(transition)
17
+ if transition.event == nil
18
+ @spontaneous_transitions.push(transition)
19
+ else
20
+ @transitions.push(transition)
21
+ end
22
+ end
26
23
 
27
24
  def context= c
28
25
  @parallel_statemachines.each do |s|
29
- s.context=c
26
+ s.context=c
30
27
  end
31
28
  end
32
29
 
33
30
  def activate(terminal_state = nil)
34
31
  @parallel_statemachines.each do |s|
35
- # next if terminal_state and s.has_state(terminal_state)
36
- @statemachine.activation.call(s.state,self.abstract_states,self.states) if @statemachine.activation
32
+ next if terminal_state and s.has_state(terminal_state)
33
+ # @statemachine.activation.call(s.state,self.abstract_states+s.abstract_states,s.state) if @statemachine.activation
37
34
  end
38
35
 
39
36
  end
@@ -48,18 +45,21 @@ module Statemachine
48
45
 
49
46
  def get_statemachine_with(id)
50
47
  @parallel_statemachines.each do |s|
51
- return s if s.has_state(id)
48
+ return s if s.has_state(id)
52
49
  end
53
50
  end
54
51
 
55
- def non_default_transition_for(event,check_superstates=true)
56
- transition = @transitions[event]
57
- return transition if transition
58
-
59
- transition = transition_for(event,check_superstates)
60
-
61
- transition = @superstate.non_default_transition_for(event) if check_superstates and @superstate and not transition
62
- return transition
52
+ def get_transitions(event)
53
+ transitions = []
54
+ @transitions.each do |t|
55
+ if t.event == event
56
+ transitions << t
57
+ end
58
+ end
59
+ if transitions.empty?
60
+ return nil
61
+ end
62
+ transitions
63
63
  end
64
64
 
65
65
  def In(id)
@@ -70,12 +70,12 @@ module Statemachine
70
70
  end
71
71
 
72
72
  def state= id
73
- @parallel_statemachines.each do |s|
73
+ @parallel_statemachines.each do |s|
74
74
  if s.has_state(id)
75
75
  s.state=id
76
76
  return true
77
77
  end
78
- end
78
+ end
79
79
  return false
80
80
  end
81
81
 
@@ -89,9 +89,6 @@ module Statemachine
89
89
  end
90
90
 
91
91
  def get_state(id)
92
- # if state = @statemachine.get_state(id)
93
- # return state
94
- # end
95
92
  @parallel_statemachines.each do |s|
96
93
  if state = s.get_state(id)
97
94
  return state
@@ -103,17 +100,24 @@ module Statemachine
103
100
  def process_event(event, *args)
104
101
  exceptions = []
105
102
  result = false
106
- # TODO fix needed: respond_to checks superstates lying out of the parallel state as well, in case an event is
107
- # defined outside the parallel statemachine it gets processed twice!
108
103
 
109
- @parallel_statemachines.each_with_index do |s,i|
104
+ # first check if the statemachine that currenlty executes the parallel state has a suitable transition
105
+ if (@statemachine.which_state_respond_to? event)
106
+ @statemachine.process_event(event,*args)
107
+ result = true
108
+ else # otherwise check for local transitions inside parallel state
109
+
110
+ @parallel_statemachines.each_with_index do |s,i|
111
+ t = s.which_state_respond_to? event
110
112
  if s.respond_to? event
111
113
  s.process_event(event,*args)
112
114
  result = true
113
115
  end
116
+ end
114
117
  end
118
+
115
119
  result
116
- end
120
+ end
117
121
 
118
122
  # Resets all of the statemachines back to theirs starting state.
119
123
  def reset
@@ -137,11 +141,11 @@ module Statemachine
137
141
  def substate_exiting(substate)
138
142
  @history_id = substate.id
139
143
  end
140
-
144
+
141
145
  def add_substates(*substate_ids)
142
146
  do_substate_adding(substate_ids)
143
147
  end
144
-
148
+
145
149
  def default_history=(state_id)
146
150
  @history_id = @default_history_id = state_id
147
151
  end
@@ -161,20 +165,28 @@ module Statemachine
161
165
  end
162
166
 
163
167
  def transition_for(event,check_superstates=true)
164
- @parallel_statemachines.each do |s|
165
- transition = s.get_state(s.state).non_default_transition_for(event,false)
166
- transition = s.get_state(s.state).default_transition if not transition
167
- return transition if transition
168
+ transition = super(event)
169
+ if not transition
170
+ @parallel_statemachines.each do |s|
171
+ transition = s.get_state(s.state).non_default_transition_for(event,false)
172
+ transition = s.get_state(s.state).default_transition if not transition
173
+ return transition if transition
174
+ end
175
+ @superstate.transition_for(event,check_superstates) if (@superstate and check_superstates and @superstate!=self)
176
+ else
177
+ transition
168
178
  end
169
- return @superstate.transition_for(event,check_superstates) if (@superstate and check_superstates and @superstate!=self)
170
-
171
- #super.transition_for(event)
172
179
  end
173
180
 
174
181
  def enter(args=[])
175
- # reset
176
- #super(args)
177
- @statemachine.state = self
182
+ @statemachine.state = self
183
+ @statemachine.trace("\tentering #{self}")
184
+
185
+ if @entry_action != nil
186
+ messenger = self.statemachine.messenger
187
+ message_queue = self.statemachine.message_queue
188
+ @statemachine.invoke_action(@entry_action, args, "entry action for #{self}", messenger, message_queue)
189
+ end
178
190
 
179
191
  @parallel_statemachines.each_with_index do |s,i|
180
192
  s.activation = @statemachine.activation
@@ -183,6 +195,39 @@ module Statemachine
183
195
  end
184
196
  end
185
197
 
198
+ def spontaneous_transition
199
+ transition = []
200
+ @parallel_statemachines.each do |s|
201
+ t = s.get_state(s.state).spontaneous_transition
202
+ transition << [t,s] if t # we need to store the state machine relevant for the transition
203
+ end
204
+ if transition.empty?
205
+ return nil
206
+ end
207
+ transition
208
+ end
209
+
210
+
211
+ def exit(args)
212
+ @statemachine.trace("\texiting #{self}")
213
+
214
+ if @exit_action != nil
215
+ messenger = self.statemachine.messenger
216
+ message_queue = self.statemachine.message_queue
217
+ @statemachine.invoke_action(@exit_action, args, "exit action for #{self}", messenger, message_queue)
218
+ @superstate.substate_exiting(self) if @superstate
219
+ end
220
+
221
+ @parallel_statemachines.each_with_index do |s,i|
222
+ as = s.get_state(s.state)
223
+ while as and as != self do
224
+ as.exit(args)
225
+ as = as.superstate
226
+ end
227
+
228
+ end
229
+ end
230
+
186
231
  def to_s
187
232
  return "'#{id}' parallel"
188
233
  end
@@ -193,8 +238,11 @@ module Statemachine
193
238
  if (@superstate)
194
239
  abstract_states=@superstate.abstract_states
195
240
  end
241
+
242
+ abstract_states += [@id]
243
+
196
244
  @parallel_statemachines.each do |s|
197
- abstract_states += s.abstract_states
245
+ abstract_states += s.abstract_states + []
198
246
  end
199
247
  abstract_states.uniq
200
248
  end