public
Description: An extendable, cloneable, dynamic UML 2 StateMachine for Ruby
Homepage: http://kurtstephens.com
Clone URL: git://github.com/kstephens/red_steak.git
Refactored Namespace and other support classes.
kstephens (author)
Wed Dec 31 01:40:03 -0800 2008
commit  2c87da86fc26f6aa65513c2ff297bd8a0e68bcf8
tree    9ad84c9c16975203b611df904b33d479b300159d
parent  1bdd51a3216fb5cc8e5d3403412d946674cba6b7
0
...
5
6
7
 
8
9
10
...
5
6
7
8
9
10
11
0
@@ -5,6 +5,7 @@
0
 ** Machine#state=(String|Array).
0
 ** Add :elapsed_time to history Hash - has performance implication.
0
 ** Support Statemachine#freeze to avoid accidental changes to a running Statemachine.
0
+** Support UML Pseudostates.
0
 
0
 * Jeremy's Comments:
0
 ** Builder#start_state and #end_state is confusing,
...
32
33
34
 
35
36
37
...
32
33
34
35
36
37
38
0
@@ -32,6 +32,7 @@ require 'red_steak/base'
0
 
0
 # UML Metamodel
0
 require 'red_steak/named_element'
0
+require 'red_steak/namespace'
0
 require 'red_steak/statemachine'
0
 require 'red_steak/vertex'
0
 require 'red_steak/state'
...
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
...
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
...
2
3
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
6
7
...
123
124
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
127
128
129
130
131
132
133
0
@@ -2,40 +2,6 @@
0
 
0
 module RedSteak
0
 
0
-  # Copies object graphs with referential integrity.
0
-  class Copier
0
-    def self.copy x
0
-      self.new.copy(x)
0
-    end
0
-
0
-    attr_reader :map
0
-
0
-    def initialize
0
-      @map = { }
0
-    end
0
-
0
-    # Copies x deeply.
0
-    #
0
-    # 1) Dups x as xx
0
-    # 2) calls xx.deepen_copy! copier, x
0
-    #
0
-    def copy x
0
-      return x if ! x
0
-
0
-      if xx = @map[x]
0
-        return xx 
0
-      end
0
-
0
-      xx = @map[x] = (x.dup rescue x)
0
-      xx.deepen_copy!(self, x) if xx.respond_to?(:deepen_copy!)
0
-
0
-      xx
0
-    end
0
-
0
-    alias :[] :copy
0
-  end
0
-
0
-
0
   # Base class for RedSteak objects.
0
   class Base 
0
     # The name of this object.
0
@@ -157,83 +123,11 @@ module RedSteak
0
   end # class
0
 
0
 
0
-  # Simple Array proxy for looking up States and Transitions by name.
0
-  class NamedArray
0
-    def initialize a = [ ], axis = nil
0
-      @a = a
0
-      @axis = axis
0
-    end
0
-
0
-
0
-    def [] pattern
0
-      case pattern
0
-      when Integer
0
-        @a[pattern]
0
-      when Array
0
-        case pattern.size
0
-        when 0
0
-          nil
0
-        when 1
0
-          self[pattern.first]
0
-        else
0
-          x = self[pattern.first]
0
-          x = x.send(@axis) if @axis
0
-          x[pattern[1 .. -1]]
0
-        end
0
-      when String
0
-        self[pattern.split(SEP).map{|x| x.to_sym}]
0
-      else
0
-        @a.find { | e | e === pattern }
0
-      end
0
-    end
0
-
0
-
0
-    def []=(i, v)
0
-      case i
0
-      when Integer
0
-        if v
0
-          @a[i] = v
0
-        else
0
-          @a.delete(i)
0
-        end
0
-      else
0
-        @a.delete_if { | x | x === i }
0
-        @a << v if v
0
-      end
0
-    end
0
-
0
-
0
-    def select &blk
0
-      self.class.new(@a.select(&blk), @axis)
0
-    end
0
-
0
-
0
-    def + x
0
-      @a + x.to_a
0
-    end
0
-
0
-
0
-    def method_missing sel, *args, &blk
0
-      @a.send(sel, *args, &blk)
0
-    end
0
-
0
-
0
-    # Deepens elements through a Copier.
0
-    def deepen_copy! copier, src
0
-      @a = @a.map { | x | copier[x] }
0
-    end
0
-
0
-
0
-    def to_a
0
-      @a
0
-    end
0
-
0
-
0
-    EMPTY = self.new([ ].freeze)
0
-  end # class
0
-
0
 end # module
0
 
0
 
0
+require 'red_steak/copier'
0
+require 'red_steak/named_array'
0
+
0
 ###############################################################################
0
 # EOF
...
278
279
280
281
 
 
282
283
284
285
286
287
 
 
 
 
 
 
288
289
290
...
300
301
302
 
303
304
305
306
307
308
309
310
311
...
322
323
324
325
326
 
327
328
329
...
345
346
347
348
 
349
350
351
...
353
354
355
356
357
 
 
 
 
 
358
359
 
360
361
362
...
373
374
375
376
 
377
378
379
 
 
380
381
382
...
278
279
280
 
281
282
283
284
285
286
 
 
287
288
289
290
291
292
293
294
295
...
305
306
307
308
309
310
 
 
311
 
312
313
314
...
325
326
327
 
 
328
329
330
331
...
347
348
349
 
350
351
352
353
...
355
356
357
 
 
358
359
360
361
362
363
 
364
365
366
367
...
378
379
380
 
381
382
 
 
383
384
385
386
387
0
@@ -278,13 +278,18 @@ module RedSteak
0
     # Determine what object should own
0
     # a new State if one is created.
0
     def _owner
0
-      @context[:statemachine]
0
+      @context[:statemachine] ||
0
+        (raise Exception, "statemachine is unknown")
0
     end
0
 
0
 
0
     # Locates a state by name or creates a new object.
0
-    def _find_state opts, create = true, owner = nil, namespaces = nil
0
-      # $stderr.puts "_find_state #{opts.inspect}, #{create.inspect} from #{caller(1).first}" if ! create
0
+    def _find_state opts, param = EMPTY_HASH
0
+      create = param[:create] != false
0
+      owner = param[:owner]
0
+      cls = param[:class] || State
0
+
0
+      _log "_find_state #{opts.inspect}, #{param.inspect}"
0
 
0
       # Parse opts.
0
       name = nil
0
@@ -300,12 +305,10 @@ module RedSteak
0
         raise ArgumentError, "invalid opts, given #{opts.inspect}"
0
       end
0
 
0
+      # Split Strings on "::"
0
       name = name.split(SEP) if String === name
0
  
0
-      # @logger = $stderr if name == :a
0
-
0
       # Determine owner.
0
-      owner ||= opts.delete(:_owner) if opts[:_owner]
0
       owner ||= _owner unless owner
0
       raise Exception, "Cannot determine owner for new State #{name.inspect}" unless owner
0
 
0
@@ -322,8 +325,7 @@ module RedSteak
0
         _log "  looking for State #{name.inspect} in path #{path.inspect}"
0
         path.each do | e |
0
           break unless owner
0
-          # $stderr.puts "  owner = #{owner.inspect}"
0
-          owner = _find_state(e.to_sym, create, owner)
0
+          owner = _find_state(e.to_sym, :owner => owner)
0
         end
0
         raise ArgumentError, "Cannot locate State #{name.inspect} in #{owner.inspect}" unless owner
0
 
0
@@ -345,7 +347,7 @@ module RedSteak
0
       _log "  state = #{state.inspect} in #{owner.inspect}"
0
 
0
 =begin
0
-      $stderr.puts "owner = #{owner.inspect}"
0
+      $stderr.puts "  owner = #{owner.inspect}"
0
       $stderr.puts "caller = #{caller(0)[0 .. 4] * "\n  "}"
0
       $stderr.puts "state = #{state.inspect}"
0
 =end
0
@@ -353,10 +355,13 @@ module RedSteak
0
       # Create a new one, if requested.
0
       if create && ! state
0
         opts[:name] = name
0
-        opts[:statemachine] ||= @context[:statemachine]
0
-        state = State.new opts
0
+        _log "  creating #{cls} #{opts.inspect} for #{owner.inspect}"
0
+        state = cls.new opts
0
+        if State === owner
0
+          owner = owner.submachine
0
+        end
0
         owner.add_state! state
0
-        _log "  created #{state.inspect}"
0
+        _log "  created #{state.inspect} for #{owner.inspect}"
0
       else
0
         if state
0
           state.options = opts
0
@@ -373,10 +378,10 @@ module RedSteak
0
       blk = t[:block]
0
       opts = t[:opts]
0
 
0
-      _log "\n\n_create_transition! #{opts.inspect}"
0
+      _log "\n\n_create_transition! #{t.inspect}"
0
 
0
-      opts[:source] = _find_state opts[:source], :create, owner
0
-      opts[:target] = _find_state opts[:target], :create, owner
0
+      opts[:source] = _find_state opts[:source], :owner => owner
0
+      opts[:target] = _find_state opts[:target], :owner => owner
0
 
0
       _log "  #{opts.inspect}"
0
        
...
3
4
5
 
 
 
6
7
8
9
10
11
 
12
13
14
...
16
17
18
 
19
20
 
 
 
 
 
 
 
 
 
21
22
23
...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
0
@@ -3,12 +3,16 @@ module RedSteak
0
 
0
   # Base class for all elements in a Statemachine.
0
   class NamedElement < Base
0
+    # The Namespace of this NamedElement.
0
+    attr_accessor :namespace
0
+
0
     # The StateMachine that owns this object.
0
     attr_accessor :stateMachine # UML
0
     alias :statemachine :stateMachine # not UML
0
     alias :statemachine= :stateMachine= # not UML
0
     
0
     def intialize opts
0
+      @namespace = nil
0
       @stateMachine = nil
0
       super
0
     end
0
@@ -16,8 +20,18 @@ module RedSteak
0
     
0
     def deepen_copy! copier, src
0
       super
0
+      @namespace = copier[@namespace]
0
       @stateMachine = copier[@stateMachine]
0
     end
0
+
0
+
0
+    
0
+    def ownedMember_added! ns
0
+    end
0
+
0
+
0
+    def ownedMember_removed! ns
0
+    end
0
     
0
     
0
     # Called by subclasses to notify/query the context object for specific actions.
...
3
4
5
6
 
 
 
 
 
 
 
 
 
 
 
7
8
9
...
3
4
5
 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0
@@ -3,7 +3,17 @@ module RedSteak
0
 
0
   # 
0
   class Pseudostate < Vertex
0
-    # See PseudostateKind
0
+    # See PseudostateKind.
0
+    # initial
0
+    # deepHistory
0
+    # shallowHistory
0
+    # join
0
+    # fork
0
+    # junction
0
+    # choice
0
+    # entryPoint
0
+    # exitPoint
0
+    # teminate
0
     attr_accessor :kind
0
 
0
     # The state associated with this Pseudostate.
...
19
20
21
 
 
 
22
23
24
...
26
27
28
 
29
30
31
...
34
35
36
 
37
38
39
...
124
125
126
127
 
 
128
129
130
...
158
159
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
162
163
...
19
20
21
22
23
24
25
26
27
...
29
30
31
32
33
34
35
...
38
39
40
41
42
43
44
...
129
130
131
 
132
133
134
135
136
...
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
0
@@ -19,6 +19,9 @@ module RedSteak
0
     # This state's submachine, or nil.
0
     attr_accessor :submachine # UML
0
 
0
+    # List of Pseudostates.
0
+    attr_reader :connectionPoint # UML
0
+
0
 
0
     def initialize opts = { }
0
       @state_type = nil
0
@@ -26,6 +29,7 @@ module RedSteak
0
       @doActivity = nil
0
       @exit = nil
0
       @submachine = nil
0
+      @connectionPoint = NamedArray.new([ ])
0
       super
0
     end
0
 
0
@@ -34,6 +38,7 @@ module RedSteak
0
       super
0
 
0
       @submachine = copier[@submachine]
0
+      @connectionPoint = copier[@connectionPoint]
0
     end
0
 
0
 
0
@@ -124,7 +129,8 @@ module RedSteak
0
           if ss = superstate
0
             x.push(*ss.ancestors)
0
           end
0
-          x
0
+          
0
+          NamedArray.new(x.freeze, :state)
0
         end
0
     end
0
 
0
@@ -158,6 +164,40 @@ module RedSteak
0
     end
0
 
0
 
0
+    # Adds a Pseudostate to this State.
0
+    def add_connectionPoint! s
0
+      _log "add_connectionPoint! #{s.inspect}"
0
+
0
+      if @connectionPoint.find { | x | x.name == s.name }
0
+        raise ArgumentError, "connectionPoint named #{s.name.inspect} already exists"
0
+      end
0
+
0
+      @ownedMember << s # ownedElement?!?!
0
+      @connectionPoint << s
0
+      s.state = self
0
+
0
+      # Notify.
0
+      s.connectionPoint_added! self
0
+
0
+      s
0
+    end
0
+
0
+
0
+    # Removes a Pseudostate from this Statemachine.
0
+    def remove_connectionPoint! s
0
+      _log "remove_Connection! #{s.inspect}"
0
+
0
+      @ownedMember.delete(s) # ownedElement?!?!
0
+      @connectionPoint.delete(s)
0
+      s.state = nil
0
+
0
+      # Notify.
0
+      s.connectionPoint_removed! self
0
+
0
+      self
0
+    end
0
+
0
+
0
     def _validate errors
0
       errors << :state_without_transitions unless transitionsize != 0
0
       case
...
2
3
4
5
 
6
7
 
 
8
9
10
11
 
 
 
 
 
12
13
14
15
16
17
18
19
20
...
52
53
54
55
56
57
58
 
59
60
61
...
114
115
116
 
117
118
119
...
132
133
134
 
135
136
137
...
154
155
156
 
157
158
159
...
168
169
170
 
171
172
173
...
274
275
276
 
 
 
 
 
277
278
279
280
281
 
282
283
284
...
2
3
4
 
5
6
 
7
8
9
10
11
 
12
13
14
15
16
17
18
19
 
 
 
20
21
22
...
54
55
56
 
 
57
58
59
60
61
62
...
115
116
117
118
119
120
121
...
134
135
136
137
138
139
140
...
157
158
159
160
161
162
163
...
172
173
174
175
176
177
178
...
279
280
281
282
283
284
285
286
287
288
289
290
 
291
292
293
294
0
@@ -2,19 +2,21 @@
0
 module RedSteak
0
 
0
   # A Statemachine object.
0
-  class Statemachine < Base
0
+  class Statemachine < Namespace
0
 
0
-    # The list of all states.
0
+    # List of State objects.
0
+    # subsets ownedMember
0
     attr_reader :states # not UML
0
     alias :state :states # UML
0
 
0
-    # The list of all transitions.
0
+    # List of Pseudostate objects.
0
+    # subsets ownedMember
0
+    attr_reader :connectionPoint # UML
0
+
0
+    # List of Transition objects.
0
     attr_reader :transitions # not UML
0
     alias :transition :transitions # UML
0
 
0
-    # The list of Pseudostates.
0
-    attr_reader :connectionPoint # UML
0
-
0
     # The enclosing State if this is a submachine.
0
     attr_accessor :submachineState # UML
0
     alias :superstate :submachineState # not UML
0
@@ -52,10 +54,9 @@ module RedSteak
0
     def deepen_copy! copier, src
0
       super
0
 
0
-      @submachineState = copier[@submachineState]
0
-
0
       @states = copier[@states]
0
       @transitions = copier[@transitions]
0
+      @submachineState = copier[@submachineState]
0
 
0
       @start_state = copier[@start_state]
0
       @end_state   = copier[@end_state]
0
@@ -114,6 +115,7 @@ module RedSteak
0
         raise ArgumentError, "state named #{s.name.inspect} already exists"
0
       end
0
 
0
+      add_ownedMember!(s)
0
       @states << s
0
       s.stateMachine = self
0
 
0
@@ -132,6 +134,7 @@ module RedSteak
0
 
0
       transitions = s.transitions
0
 
0
+      remove_ownedMember!(s)
0
       @states.delete(s)
0
       s.stateMachine = nil
0
 
0
@@ -154,6 +157,7 @@ module RedSteak
0
         raise ArgumentError, "connectionPoint named #{s.name.inspect} already exists"
0
       end
0
 
0
+      @ownedMember << s
0
       @connectionPoint << s
0
       s.stateMachine = self
0
 
0
@@ -168,6 +172,7 @@ module RedSteak
0
     def remove_connectionPoint! s
0
       _log "remove_Connection! #{s.inspect}"
0
 
0
+      @ownedMember.delete(s)
0
       @connectionPoint.delete(s)
0
       s.stateMachine = nil
0
 
0
@@ -274,11 +279,16 @@ module RedSteak
0
 
0
     ##################################################################
0
 
0
+    def inspect
0
+      "#<#{self.class} #{to_s}>"
0
+    end
0
+
0
+
0
 
0
     def _log *args
0
       case 
0
       when IO === @logger
0
-        @logger.puts "#{self.to_a.inspect} #{(state && state.to_a).inspect} #{args * " "}"
0
+        @logger.puts "#{self.to_s} #{args * " "}"
0
       when defined?(::Log4r) && (Log4r::Logger === @logger)
0
         @logger.send(log_level || :debug, *args)
0
       when (x = superstatemachine)
...
2
3
4
5
 
6
7
8
...
62
63
64
65
 
66
67
68
...
2
3
4
 
5
6
7
8
...
62
63
64
 
65
66
67
68
0
@@ -2,7 +2,7 @@
0
 module RedSteak
0
 
0
   # Represents a transition from one state to another state in a statemachine.
0
-  class Transition < NamedElement
0
+  class Transition < Namespace
0
     # See TransitionKind.
0
     attr_accessor :kind
0
 
0
@@ -62,7 +62,7 @@ module RedSteak
0
 
0
 
0
     def inspect
0
-      "#<#{self.class} #{stateMachine.to_s} #{name} #{source.to_s} -> #{target.to_s}>" 
0
+      "#<#{self.class} #{@stateMachine.to_s} #{name} #{source.to_s} -> #{target.to_s}>" 
0
     end
0
 
0
   end # class
...
114
115
116
117
 
118
119
120
...
114
115
116
 
117
118
119
120
0
@@ -114,7 +114,7 @@ module RedSteak
0
     # Returns an Array representation of this State.
0
     # Includes superstates and substates.
0
     def to_a
0
-      if ss = @stateMachine.submachineState
0
+      if ss = @stateMachine && @stateMachine.submachineState
0
         ss.to_a << @name
0
       else
0
         [ @name ]
...
105
106
107
108
 
109
110
111
112
 
113
114
115
...
208
209
210
211
 
212
213
214
...
527
528
529
 
530
531
532
 
 
533
534
535
...
105
106
107
 
108
109
110
111
 
112
113
114
115
...
208
209
210
 
211
212
213
214
...
527
528
529
530
531
 
 
532
533
534
535
536
0
@@ -105,11 +105,11 @@ describe RedSteak do
0
     # There can only one.
0
     return Thread.current[:statemachine] if Thread.current[:statemachine]
0
 
0
-    b = RedSteak::Builder.new
0
+    b = RedSteak::Builder.new(:logger => false && $stderr)
0
     # breakpointer
0
     
0
     b.build do
0
-      statemachine :test do
0
+      statemachine :test, :logger => false && $stderr do
0
         initial :a
0
         final :end
0
     
0
@@ -208,7 +208,7 @@ describe RedSteak do
0
     e.targets.to_a.should == [ ]
0
     e.sources.to_a.map{|s| s.to_s}.should == [ 'c', 'd', 'd::end' ]
0
 
0
-    sm.states.find{|s| s.name == :a}.options[:option_foo].should == :foo
0
+    sm.states[:a].options[:option_foo].should == :foo
0
 
0
     sm.validate.should == [ ]
0
 
0
@@ -527,9 +527,10 @@ describe RedSteak do
0
 
0
 
0
   it 'should handle transitions across substates and states' do
0
+    logger = nil && $stderr
0
     sm = RedSteak::Statemachine.
0
-      new(:name => :test2).
0
-      build(:logger => false && $stderr) do
0
+      new(:name => :test2, :logger => logger).
0
+      build(:logger => logger) do
0
       initial :a
0
       final :end
0
       

Comments