Class: IDRegistry::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/idregistry/registry.rb

Overview

A registry object.

Instance Method Summary collapse

Constructor Details

#initialize(patterns_, types_, categories_, methods_) ⇒ Registry

:nodoc:



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/idregistry/registry.rb', line 45

def initialize(patterns_, types_, categories_, methods_)  # :nodoc:
  @patterns = patterns_
  @types = types_
  @categories = categories_
  @methods = methods_
  @tuples = {}
  @objects = {}
  @catdata = {}
  @config = Configuration._new(self, @patterns, @types, @categories, @methods)
  @mutex = ::Mutex.new
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name_, *args_) ⇒ Object

Implement convenience methods.



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/idregistry/registry.rb', line 401

def method_missing(name_, *args_)  # :nodoc:
  if (method_info_ = @methods[name_])
    tuple_ = method_info_[0].dup
    indexes_ = method_info_[1]
    case indexes_
    when ::Array
      lookup_args_ = args_.size == indexes_.size + 1 ? args_.pop : {}
      if lookup_args_.is_a?(::Hash) && args_.size == indexes_.size
        args_.each_with_index do |a_, i_|
          if (j_ = indexes_[i_])
            tuple_[j_] = a_
          end
        end
        return lookup(tuple_, lookup_args_)
      end
    when ::Hash
      lookup_args_ = args_.size == 2 ? args_.pop : {}
      if lookup_args_.is_a?(::Hash) && args_.size == 1
        arg_ = args_[0]
        if arg_.is_a?(::Hash)
          arg_.each do |k_, v_|
            if (j_ = indexes_[k_])
              tuple_[j_] = v_
            end
          end
          return lookup(tuple_, lookup_args_)
        end
      end
    end
  end
  super
end

Instance Method Details

#add(type_, object_) ⇒ Object

Add the given object to the registry. You must specify the type of object, which is used to determine what tuples correspond to it.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/idregistry/registry.rb', line 260

def add(type_, object_)
  @config.lock

  # Some sanity checks of the arguments.
  if object_.nil?
    raise ObjectKeyError, "Attempt to add a nil object"
  end
  unless @types.has_key?(type_)
    raise ObjectKeyError, "Unrecognized type: #{type_}"
  end

  # Synchronize the actual add to protect against concurrent mutation.
  @mutex.synchronize do
    _internal_add(type_, object_, nil, nil)
  end
  self
end

#categories(arg_) ⇒ Object

Return all the categories for the given object or tuple.

If you pass an Array, it is interpreted as a tuple. If you pass something other than an Array or a Hash, it is interpreted as an object. Otherwise, you can explicitly specify whether you are passing a tuple or object by using hash named arguments, e.g. :tuple =>, or :object =>.

The return value is a hash. The keys are the category types relevant to this object. The values are the value arrays indicating which category the object falls under for each type.



150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/idregistry/registry.rb', line 150

def categories(arg_)
  @config.lock

  objdata_ = _get_objdata(arg_)
  return nil unless objdata_
  hash_ = {}
  objdata_[2].each do |tup_, tupcats_|
    tupcats_.each do |cat_|
      hash_[cat_] = @categories[cat_][1].map{ |elem_| tup_[elem_] }
    end
  end
  hash_
end

#clearObject

Clear out all cached objects from the registry.



389
390
391
392
393
394
395
396
# File 'lib/idregistry/registry.rb', line 389

def clear
  @mutex.synchronize do
    @tuples.clear
    @objects.clear
    @catdata.clear
  end
  self
end

#config(&block_) ⇒ Object

Get the configuration for this registry.

You may also configure this registry by providing a block. The configuration object will then be available as a DSL.



68
69
70
71
# File 'lib/idregistry/registry.rb', line 68

def config(&block_)
  ::Blockenspiel.invoke(block_, @config) if block_
  @config
end

#delete(arg_) ⇒ Object

Delete the given object.

If you pass an Array, it is interpreted as a tuple. If you pass something other than an Array or a Hash, it is interpreted as an object. Otherwise, you can explicitly specify whether you are passing a tuple or object by using hash named arguments, e.g. :tuple =>, or :object =>.



288
289
290
291
292
293
294
295
296
297
298
# File 'lib/idregistry/registry.rb', line 288

def delete(arg_)
  @config.lock

  @mutex.synchronize do
    if (objdata_ = _get_objdata(arg_))
      @objects.delete(objdata_[0].object_id)
      objdata_[2].each_key{ |tup_| _remove_tuple(objdata_, tup_) }
    end
  end
  self
end

#delete_category(category_type_, *category_spec_) ⇒ Object

Delete all objects in a given category, which is specified by the category type and the value array indicating which category of that type.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/idregistry/registry.rb', line 305

def delete_category(category_type_, *category_spec_)
  @config.lock

  if @categories.include?(category_type_)
    spec_ = category_spec_.size == 1 && category_spec_.first.is_a?(::Array) ? category_spec_.first : category_spec_
    if (tuple_hash_ = (@catdata[category_type_] ||= {})[spec_])
      @mutex.synchronize do
        tuple_hash_.values.each do |objdata_|
          @objects.delete(objdata_[0].object_id)
          objdata_[2].each_key{ |tup_| _remove_tuple(objdata_, tup_) }
        end
      end
    end
  end
  self
end

#get(tuple_) ⇒ Object

Retrieve the cached object corresponding to the given tuple. Returns nil if the object is not currently cached. Does not attempt to generate the object for you.



100
101
102
103
# File 'lib/idregistry/registry.rb', line 100

def get(tuple_)
  objdata_ = @tuples[tuple_]
  objdata_ ? objdata_[0] : nil
end

#include?(arg_) ⇒ Boolean

Returns true if the given object or tuple is present.

If you pass an Array, it is interpreted as a tuple. If you pass something other than an Array or a Hash, it is interpreted as an object. Otherwise, you can explicitly specify whether you are passing a tuple or object by using hash named arguments, e.g. :tuple =>, or :object =>.

Returns:

  • (Boolean)


132
133
134
# File 'lib/idregistry/registry.rb', line 132

def include?(arg_)
  _get_objdata(arg_) ? true : false
end

#inspectObject

:nodoc:



58
59
60
# File 'lib/idregistry/registry.rb', line 58

def inspect  # :nodoc:
  "#<#{self.class}:0x#{object_id.to_s(16)} size=#{size}>"
end

#lookup(*args_) ⇒ Object

Get the object corresponding to the given tuple. If the tuple is not present, the registry tries to generate the object for you. Returns nil if it is unable to do so.

You may pass the tuple as a single array argument, or as a set of arguments.

If the last argument is a hash, it is removed from the tuple and treated as an options hash that may be passed to an object generator block.



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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/idregistry/registry.rb', line 204

def lookup(*args_)
  opts_ = args_.last.is_a?(::Hash) ? args_.pop : {}
  tuple_ = args_.size == 1 && args_.first.is_a?(::Array) ? args_.first : args_

  @config.lock

  # Fast-track lookup if it's already there
  if (objdata_ = @tuples[tuple_])
    return objdata_[0]
  end

  # Not there for now. Try to create the object.
  # We want to do this before entering the synchronize block because
  # we don't want callbacks called within the synchronization.
  obj_ = nil
  type_ = nil
  pattern_ = nil
  @patterns.each do |pat_, patdata_|
    if Utils.matches?(pat_, tuple_)
      block_ = patdata_[1]
      obj_ = case block_.arity
        when 0 then block_.call
        when 1 then block_.call(tuple_)
        when 2 then block_.call(tuple_, self)
        else block_.call(tuple_, self, opts_)
      end
      unless obj_.nil?
        pattern_ = pat_
        type_ = patdata_[0]
        break
      end
    end
  end

  if obj_
    # Now attempt to insert the object.
    # This part is synchronized to protect against concurrent mutation.
    # Once in the synchronize block, we also double-check that no other
    # thread added the object in the meantime. If another thread did,
    # we throw away the object we just created, and return the other
    # thread's object instead.
    @mutex.synchronize do
      if (objdata_ = @tuples[tuple_])
        obj_ = objdata_[0]
      else
        _internal_add(type_, obj_, tuple_, pattern_)
      end
    end
  end
  obj_
end

#objects_in_category(category_type_, *category_spec_) ⇒ Object

Return all objects in a given category, which is specified by the category type and the value array indicating which category of that type.



169
170
171
172
173
174
175
176
# File 'lib/idregistry/registry.rb', line 169

def objects_in_category(category_type_, *category_spec_)
  @config.lock

  return nil unless @categories.include?(category_type_)
  spec_ = category_spec_.size == 1 && category_spec_.first.is_a?(::Array) ? category_spec_.first : category_spec_
  tuple_hash_ = (@catdata[category_type_] ||= {})[spec_]
  tuple_hash_ ? tuple_hash_.values.map{ |objdata_| objdata_[0] } : []
end

#rekey(arg_) ⇒ Object

Recompute the tuples for the given object, which may be identified by object or tuple. Call this when the value of the object changes in such a way that the registry should identify it differently.

If you pass an Array, it is interpreted as a tuple. If you pass something other than an Array or a Hash, it is interpreted as an object. Otherwise, you can explicitly specify whether you are passing a tuple or object by using hash named arguments, e.g. :tuple =&gt;, or :object =&gt;.



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/idregistry/registry.rb', line 334

def rekey(arg_)
  @config.lock

  # Resolve the object.
  if (objdata_ = _get_objdata(arg_))

    # Look up tuple generators from the type, and determine the
    # new tuples for the object.
    # Do this before entering the synchronize block because we
    # don't want callbacks called within the synchronization.
    obj_ = objdata_[0]
    type_ = objdata_[1]
    new_tuple_list_ = []
    @types[type_].each do |pat_|
      if (block_ = @patterns[pat_][2])
        new_tuple_ = block_.call(obj_)
        new_tuple_list_ << new_tuple_ if new_tuple_
      else
        raise ObjectKeyError, "Not all patterns for this type can generate tuples"
      end
    end

    # Synchronize to protect against concurrent mutation.
    @mutex.synchronize do
      # One last check to ensure the object is still present
      if @objects.has_key?(obj_.object_id)
        # Ensure none of the new tuples isn't pointed elsewhere already.
        # Tuples pointed at this object, ignore them.
        # Tuples pointed at another object, raise an error.
        tuple_hash_ = objdata_[2]
        new_tuple_list_.delete_if do |tup_|
          if tuple_hash_.has_key?(tup_)
            true
          elsif @tuples.has_key?(tup_)
            raise ObjectKeyError, "Could not rekey because one of the new tuples is already present"
          else
            false
          end
        end
        # Now go through and edit the tuples
        (tuple_hash_.keys - new_tuple_list_).each do |tup_|
          _remove_tuple(objdata_, tup_)
        end
        new_tuple_list_.each do |tup_|
          _add_tuple(objdata_, tup_)
        end
      end
    end
  end
  self
end

#respond_to?(name_) ⇒ Boolean

Make sure respond_to does the right thing for convenience methods

Returns:

  • (Boolean)


437
438
439
# File 'lib/idregistry/registry.rb', line 437

def respond_to?(name_)  # :nodoc:
  super || @methods.include?(name_.to_sym)
end

#sizeObject

Return the number of objects cached in the registry.



91
92
93
# File 'lib/idregistry/registry.rb', line 91

def size
  @objects.size
end

#spawn_registry(opts_ = {}) ⇒ Object

Create a new empty registry, duplicating this registry’s configuration.

If the :unlocked option is set to true, the new registry will have an unlocked configuration that can be modified further. Otherwise, the new registry’s configuration will be locked.

Spawning a locked registry from a locked configuration is very fast because it reuses the configuration objects.



84
85
86
# File 'lib/idregistry/registry.rb', line 84

def spawn_registry(opts_={})
  config.spawn_registry(opts_)
end

#tuples_for(arg_) ⇒ Object

Returns an array of all tuples corresponding to the given object, or the object identified by the given tuple. Returns nil if the given object is not cached in the registry.

If you pass an Array, it is interpreted as a tuple. If you pass something other than an Array or a Hash, it is interpreted as an object. Otherwise, you can explicitly specify whether you are passing a tuple or object by using hash named arguments, e.g. :tuple =&gt;, or :object =&gt;.



117
118
119
120
# File 'lib/idregistry/registry.rb', line 117

def tuples_for(arg_)
  objdata_ = _get_objdata(arg_)
  objdata_ ? objdata_[2].keys : nil
end

#tuples_in_category(category_type_, *category_spec_) ⇒ Object

Return all tuples in a given category, which is specified by the category type and the value array indicating which category of that type.



183
184
185
186
187
188
189
190
# File 'lib/idregistry/registry.rb', line 183

def tuples_in_category(category_type_, *category_spec_)
  @config.lock

  return nil unless @categories.include?(category_type_)
  spec_ = category_spec_.size == 1 && category_spec_.first.is_a?(::Array) ? category_spec_.first : category_spec_
  tuple_hash_ = (@catdata[category_type_] ||= {})[spec_]
  tuple_hash_ ? tuple_hash_.keys : []
end