MINT-statemachine 1.2.3 → 1.3.0

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.
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