Class: Roby::ExtendedStruct

Inherits:
Object show all
Includes:
DRbUndumped
Defined in:
lib/roby/state/state.rb

Overview

ExtendedStruct objects are OpenStructs where attributes have a default class. They are used to build hierarchical data structure on-the-fly

For instance root = ExtendedStruct.new root.child.value = 42

However, you cannot check if a value is defined or not with if (root.child)

<do something>

end

You’ll have to test with respond_to? or #name?. The second one will return true only if the attribute is defined and it is not false if (root.respond_to?(:child)

<do something>

end

Handling of methods defined on parents

Methods defined in Object or Kernel are automatically overriden if needed. For instance, if you’re managing a (x, y, z) position using ExtendedStruct, you will want YAML#y to not get in the way. The exceptions are the methods listed in NOT_OVERRIDABLE

Direct Known Subclasses

StateSpace

Constant Summary collapse

FORBIDDEN_NAMES =
%w{marshal each enum to}.map { |str| "^#{str}_" }
FORBIDDEN_NAMES_RX =
/(?:#{FORBIDDEN_NAMES.join("|")})/
NOT_OVERRIDABLE =
%w{class} + instance_methods(false)
NOT_OVERRIDABLE_RX =
/(?:#{NOT_OVERRIDABLE.join("|")})/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(children_class = ExtendedStruct, attach_to = nil, attach_name = nil) ⇒ ExtendedStruct

attach_to and attach_name are used so that

root = ExtendedStruct.new
root.bla

does not add a bla attribute to root, while the following constructs

root.bla.test = 20
bla = root.bla
bla.test = 20

does

Note, however that

bla = root.bla
root.bla = 10
bla.test = 20

will not make root.bla be the bla object. And that

bla = root.bla
root.stable!
bla.test = 20

will not fail



52
53
54
55
56
57
# File 'lib/roby/state/state.rb', line 52

def initialize(children_class = ExtendedStruct, attach_to = nil, attach_name = nil) # :nodoc
      clear
    @attach_as = [attach_to, attach_name.to_s] if attach_to
      @children_class = children_class
    @observers       = Hash.new { |h, k| h[k] = [] }
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &update) ⇒ Object

:nodoc:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/roby/state/state.rb', line 265

def method_missing(name, *args, &update) # :nodoc:
      name = name.to_s

      super(name.to_sym, *args, &update) if name =~ FORBIDDEN_NAMES_RX
    if name =~ /(.+)=$/
    # Setter
    name = $1

    value = *args
        if stable?
            raise NoMethodError, "#{self} is stable"
    elsif @filters.has_key?(name) && !@filters[name].call(value)
        raise ArgumentError, "value #{value} is not valid for #{name}"
    elsif !@members.has_key?(name) && !@aliases.has_key?(name) && respond_to?(name)
        if NOT_OVERRIDABLE_RX =~ name
      raise ArgumentError, "#{name} is already defined an cannot be overriden"
        end

        # Override it
        singleton_class.class_eval { private name }
    end

    attach


    @aliases.delete(name)
    pending = @pending.delete(name)

    if pending && pending != value
        pending.detach
    end

    @members[name] = value
    updated(name, value)
        return value

      elsif name =~ /(.+)\?$/
    # Test
    name = $1
    respond_to?(name) && send(name)

    elsif args.empty? # getter
    attach

    if @members.has_key?(name)
        member = @members[name]
    else
        if alias_to = @aliases[name]
      return send(alias_to)
        elsif stable?
      raise NoMethodError, "no such attribute #{name} (#{self} is stable)"
        else
      member = children_class.new(children_class, self, name)
      @pending[name] = member
        end
    end

        if update
            member.update(&update)
    else
            member
        end

      else
    super(name.to_sym, *args, &update)
    end
end

Instance Attribute Details

#__parent_nameObject (readonly)

Returns the value of attribute __parent_name.



100
101
102
# File 'lib/roby/state/state.rb', line 100

def __parent_name
  @__parent_name
end

#__parent_structObject (readonly)

Returns the value of attribute __parent_struct.



100
101
102
# File 'lib/roby/state/state.rb', line 100

def __parent_struct
  @__parent_struct
end

#children_classObject (readonly)

Returns the value of attribute children_class.



98
99
100
# File 'lib/roby/state/state.rb', line 98

def children_class
  @children_class
end

Class Method Details

._load(io) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/roby/state/state.rb', line 68

def self._load(io)
    marshalled_members, aliases = Marshal.load(io)
    members = marshalled_members.inject({}) do |h, (n, mv)|
  begin
      h[n] = Marshal.load(mv)
  rescue Exception
      Roby::Distributed.warn "cannot load #{n} #{mv}: #{$!.message}"
  end

  h
    end

    result = ExtendedStruct.new
    result.instance_variable_set("@members", members)
    result.instance_variable_set("@aliases", aliases)
    result

rescue Exception
    Roby::Distributed.warn "cannot load #{members} #{io}: #{$!.message}"
    raise
end

Instance Method Details

#__respond_to__(name) ⇒ Object

1.8.7’s #respond_to? takes two arguments, 1.8.6 only one. This is the common implementation for both version. #respond_to? is adapted (see above)



236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/roby/state/state.rb', line 236

def __respond_to__(name) # :nodoc:
    name = name.to_s
      return false if name =~ FORBIDDEN_NAMES_RX

    if name =~ /=$/
    !@stable
    else
        if @members.has_key?(name)
        true
    else
        (alias_to = @aliases[name]) && respond_to?(alias_to)
    end
    end
end

#_dump(lvl = -1)) ⇒ Object



90
91
92
93
94
95
96
# File 'lib/roby/state/state.rb', line 90

def _dump(lvl = -1)
    marshalled_members = @members.map do |name, value|
  [name, Marshal.dump(value)] rescue nil
    end
    marshalled_members.compact!
    Marshal.dump([marshalled_members, @aliases])
end

#alias(from, to) ⇒ Object



333
334
335
# File 'lib/roby/state/state.rb', line 333

def alias(from, to)
    @aliases[to.to_s] = from.to_s
end

#attachObject

:nodoc:



101
102
103
104
105
106
107
# File 'lib/roby/state/state.rb', line 101

def attach # :nodoc:
      if @attach_as
    @__parent_struct, @__parent_name = @attach_as
    @attach_as = nil
    __parent_struct.attach_child(__parent_name, self)
      end
end

#attach_child(name, obj) ⇒ Object



111
112
113
# File 'lib/roby/state/state.rb', line 111

def attach_child(name, obj)
    @members[name.to_s] = obj
end

#clearObject



59
60
61
62
63
64
65
66
# File 'lib/roby/state/state.rb', line 59

def clear
    @attach_as       = nil
           @stable          = false
           @members         = Hash.new
           @pending         = Hash.new
           @filters         = Hash.new
           @aliases         = Hash.new
end

#delete(name = nil) ⇒ Object

Raises:

  • (TypeError)


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
# File 'lib/roby/state/state.rb', line 157

def delete(name = nil)
    raise TypeError, "#{self} is stable" if stable?
    if name
  name = name.to_s
  if child = @members.delete(name)
      child.instance_variable_set(:@__parent_struct, nil)
      child.instance_variable_set(:@__parent_name, nil)
  elsif child = @pending.delete(name)
      child.instance_variable_set(:@attach_as, nil)
  elsif child = @aliases.delete(name)
      # nothing to do here
  else
      raise ArgumentError, "no such child #{name}"
  end

  # and remove aliases that point to +name+
  @aliases.delete_if { |_, pointed_to| pointed_to == name }
    else
  if __parent_struct
      __parent_struct.delete(__parent_name)
  elsif @attach_as
      @attach_as.first.delete(@attach_as.last)
  else
      raise ArgumentError, "#{self} is attached to nothing"
  end
    end
end

#each_member(&block) ⇒ Object

Iterates on all defined members of this object



137
138
139
# File 'lib/roby/state/state.rb', line 137

def each_member(&block)
    @members.each(&block)
end

#empty?Boolean

Returns true if this object has no member

Returns:

  • (Boolean)


218
# File 'lib/roby/state/state.rb', line 218

def empty?; @members.empty? end

#filter(name, &block) ⇒ Object

Define a filter for the name attribute on self. The given block is called when the attribute is written, and should return true if the new value if valid or false otherwise



188
189
190
# File 'lib/roby/state/state.rb', line 188

def filter(name, &block)
    @filters[name.to_s] = block
end

#get(name, default_value) ⇒ Object



251
252
253
254
255
256
257
# File 'lib/roby/state/state.rb', line 251

def get(name, default_value)
    if respond_to?(name)
  send(name.to_sym)
    else
  default_value
    end
end

#on(name = nil, &block) ⇒ Object

Call block with the new value if name changes



117
118
119
120
# File 'lib/roby/state/state.rb', line 117

def on(name = nil, &block)
    name = name.to_s if name
    @observers[name] << block
end

#respond_to?(name) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


221
222
223
224
# File 'lib/roby/state/state.rb', line 221

def respond_to?(name, include_private = false) # :nodoc:
    return true  if super
    return __respond_to__(name)
end

#stable!(recursive = false, is_stable = true) ⇒ Object

Sets the stable attribute of self to is_stable. If recursive is true, set it on the child struct as well.



199
200
201
202
203
204
# File 'lib/roby/state/state.rb', line 199

def stable!(recursive = false, is_stable = true)
    @stable = is_stable
    if recursive
        @members.each { |name, object| object.stable!(recursive, is_stable) if object.respond_to?(:stable!) }
    end
end

#stable?Boolean

If self is stable, it cannot be updated. That is, calling a setter method raises NoMethodError

Returns:

  • (Boolean)


194
# File 'lib/roby/state/state.rb', line 194

def stable?; @stable end

#to_hash(recursive = true) ⇒ Object

Converts this ExtendedStruct into a corresponding hash, where all keys are symbols. If recursive is true, any member which responds to #to_hash will be converted as well



125
126
127
128
129
130
131
132
133
134
# File 'lib/roby/state/state.rb', line 125

def to_hash(recursive = true)
    result = Hash.new
    @members.each do |k, v|
  result[k.to_sym] = if recursive && v.respond_to?(:to_hash)
             v.to_hash
         else v
         end
    end
    result
end

#update(hash = nil) {|_self| ... } ⇒ Object

Update a set of values on this struct If a hash is given, it is an name => value hash of attribute values. A given block is yield with self, so that the construct

my.extendable.struct.very.deep.update do |deep|
  <update deep>
end

can be used

Yields:

  • (_self)

Yield Parameters:



150
151
152
153
154
155
# File 'lib/roby/state/state.rb', line 150

def update(hash = nil)
      attach
    hash.each { |k, v| send("#{k}=", v) } if hash
    yield(self) if block_given?
    self
end

#updated(name, value) ⇒ Object



206
207
208
209
210
211
212
213
214
215
# File 'lib/roby/state/state.rb', line 206

def updated(name, value)
    if @observers.has_key?(name)
  @observers[name].each { |b| b.call(value) }
    end
    @observers[nil].each { |b| b.call(value) }

    if __parent_struct
  __parent_struct.updated(__parent_name, self)
    end
end