File: abstract_reference_value_map.rb

package info (click to toggle)
ruby-ref 1.0.5%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 332 kB
  • ctags: 186
  • sloc: ruby: 1,107; java: 92; makefile: 2
file content (127 lines) | stat: -rw-r--r-- 3,493 bytes parent folder | download
1
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
module Ref
  # Abstract base class for WeakValueMap and SoftValueMap.
  #
  # The classes behave similar to Hashes, but the values in the map are not strong references
  # and can be reclaimed by the garbage collector at any time. When a value is reclaimed, the
  # map entry will be removed.
  class AbstractReferenceValueMap
    class << self
      def reference_class=(klass) #:nodoc:
        @reference_class = klass
      end
    
      def reference_class #:nodoc:
        raise NotImplementedError.new("#{name} is an abstract class and cannot be instantiated") unless @reference_class
        @reference_class
      end
    end
    
    # Create a new map. Values added to the map will be cleaned up by the garbage
    # collector if there are no other reference except in the map.
    def initialize
      @references = {}
      @references_to_keys_map = {}
      @lock = SafeMonitor.new
      @reference_cleanup = lambda{|object_id| remove_reference_to(object_id)}
    end

    # Get a value from the map by key. If the value has been reclaimed by the garbage
    # collector, this will return nil.
    def [](key)
      ref = @references[key]
      value = ref.object if ref
      value
    end

    # Add a key/value to the map.
    def []=(key, value)
      ObjectSpace.define_finalizer(value, @reference_cleanup)
      key = key.dup if key.is_a?(String)
      @lock.synchronize do
        @references[key] = self.class.reference_class.new(value)
        keys_for_id = @references_to_keys_map[value.__id__]
        unless keys_for_id
          keys_for_id = []
          @references_to_keys_map[value.__id__] = keys_for_id
        end
        keys_for_id << key
      end
      value
    end

    # Remove the entry associated with the key from the map.
    def delete(key)
      ref = @references.delete(key)
      if ref
        keys_to_id = @references_to_keys_map[ref.referenced_object_id]
        if keys_to_id
          keys_to_id.delete(key)
          @references_to_keys_map.delete(ref.referenced_object_id) if keys_to_id.empty?
        end
        ref.object
      else
        nil
      end
    end

    # Get the list of all values that have not yet been garbage collected.
    def values
      vals = []
      each{|k,v| vals << v}
      vals
    end
    
    # Turn the map into an arry of [key, value] entries
    def to_a
      array = []
      each{|k,v| array << [k, v]}
      array
    end
    
    # Iterate through all the key/value pairs in the map that have not been reclaimed
    # by the garbage collector.
    def each
      @references.each do |key, ref|
        value = ref.object
        yield(key, value) if value
      end
    end

    # Clear the map of all key/value pairs.
    def clear
      @lock.synchronize do
        @references.clear
        @references_to_keys_map.clear
      end
    end

    # Merge the values from another hash into this map.
    def merge!(other_hash)
      other_hash.each do |key, value|
        self[key] = value
      end
    end

    def inspect
      live_entries = {}
      each do |key, value|
        live_entries[key] = value
      end
      live_entries.inspect
    end

    private

      def remove_reference_to(object_id)
        @lock.synchronize do
          keys = @references_to_keys_map[object_id]
          if keys
            keys.each do |key|
              @references.delete(key)
            end
            @references_to_keys_map.delete(object_id)
          end
        end
      end
  end
end