idregistry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # IDRegistry exceptions
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module IDRegistry
38
+
39
+
40
+ # Base class for IDRegistry exceptions
41
+
42
+ class IDRegistryError < ::StandardError
43
+ end
44
+
45
+
46
+ # Raised if you attempt to modify the configuration of a registry for which
47
+ # the configuration has been locked because you've started to add data.
48
+
49
+ class ConfigurationLockedError < IDRegistryError
50
+ end
51
+
52
+
53
+ # Raised if you attempt to make an illegal modification to a configuration.
54
+
55
+ class IllegalConfigurationError < IDRegistryError
56
+ end
57
+
58
+
59
+ # Raised if you attempt to add a nil object to the registry, or if you
60
+ # add an object that is already present under a different tuple or type.
61
+
62
+ class ObjectKeyError < IDRegistryError
63
+ end
64
+
65
+
66
+ end
@@ -0,0 +1,144 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # IDRegistry middleware
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module IDRegistry
38
+
39
+
40
+ # A Rack middleware that manages registries around a request.
41
+ #
42
+ # Configure this middleware with a set of registry-related tasks,
43
+ # such as creating temporary registries scoped to the request, or
44
+ # clearing registries at the end of the request.
45
+ #
46
+ # A task object must include two methods: pre and post.
47
+ # These methods are called before and after the request, and are
48
+ # passed the Rack environment hash.
49
+
50
+ class RegistryMiddleware
51
+
52
+
53
+ # A registry task that clears a registry at the end of a request.
54
+ #
55
+ # You may also provide an optional condition block, which is called
56
+ # and passed the Rack env to determine whether the registry should
57
+ # be cleared. If no condition block is provided, the registry is
58
+ # always cleared.
59
+
60
+ class ClearRegistry
61
+
62
+ # Create a new ClearRegistry task. You must provide the registry
63
+ # and an optional condition block.
64
+ def initialize(registry_, &condition_)
65
+ @condition = condition_
66
+ @registry = registry_
67
+ end
68
+
69
+ # The pre method for this task does nothing.
70
+ def pre(env_)
71
+ end
72
+
73
+ # The post method for this task clears the registry if the
74
+ # condition block passes
75
+ def post(env_)
76
+ if !@condition || @condition.call(env_)
77
+ @registry.clear
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+
84
+ # A registry task that spawns a registry scoped to this request.
85
+ #
86
+ # You must provide a locked registry configuration to use as a
87
+ # template. The spawned registry will use the given configuration.
88
+ # You must also provide a key, which will be used to store the
89
+ # spawned registry in the Rack environment so that your application
90
+ # can access it.
91
+
92
+ class SpawnRegistry
93
+
94
+ # Create a new ClearRegistry task. You must provide a locked
95
+ # template configuration and a key into the Rack environment.
96
+ def initialize(template_, envkey_)
97
+ @template = template_
98
+ @envkey = envkey_
99
+ @registry = nil
100
+ end
101
+
102
+ # The pre method for this task creates a new registry.
103
+ def pre(env_)
104
+ @registry = env_[@envkey] = @template.spawn_registry
105
+ end
106
+
107
+ # The post method for this task clears the spawned registry.
108
+ def post(env_)
109
+ if @registry
110
+ @registry.clear
111
+ @registry = nil
112
+ env_.delete(@envkey)
113
+ end
114
+ end
115
+
116
+ end
117
+
118
+
119
+ # Create a middleware.
120
+ #
121
+ # After the required Rack app argument, provide an array of tasks.
122
+
123
+ def initialize(app_, tasks_=[], opts_={})
124
+ @app = app_
125
+ @tasks = tasks_
126
+ end
127
+
128
+
129
+ # Wrap the Rack app with registry tasks.
130
+
131
+ def call(env_)
132
+ begin
133
+ @tasks.each{ |task_| task_.pre(env_) }
134
+ return @app.call(env_)
135
+ ensure
136
+ @tasks.each{ |task_| task_.post(env_) }
137
+ end
138
+ end
139
+
140
+
141
+ end
142
+
143
+
144
+ end
@@ -0,0 +1,123 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # IDRegistry railtie
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ require 'idregistry'
38
+ require 'rails/railtie'
39
+
40
+
41
+ module IDRegistry
42
+
43
+
44
+ # This railtie installs and configures a middleware that helps you
45
+ # manage registries around Rails requests. See RegistryMiddleware for
46
+ # details.
47
+ #
48
+ # To install into a Rails app, include this line in your
49
+ # config/application.rb:
50
+ # require 'idregistry/railtie'
51
+ # It should appear before your application configuration.
52
+ #
53
+ # You can then configure it using the standard rails configuration
54
+ # mechanism. The configuration lives in the config.idregistry
55
+ # configuration namespace. See IDRegistry::Railtie::Configuration for
56
+ # the configuration options.
57
+
58
+ class Railtie < ::Rails::Railtie
59
+
60
+
61
+ # Configuration options. These are methods on config.idregistry.
62
+
63
+ class Configuration
64
+
65
+ def initialize # :nodoc:
66
+ @tasks = []
67
+ @after_middleware = nil
68
+ end
69
+
70
+
71
+ # Array of registry tasks
72
+ attr_accessor :tasks
73
+
74
+ # Middleware to run before, or nil to run the middleware toward the end
75
+ attr_accessor :before_middleware
76
+
77
+
78
+ # Set up the middleware to clear the given registry after each
79
+ # request.
80
+ #
81
+ # If you provide the optional block, it is called and passed the
82
+ # Rack environment. The registry is cleared only if the block
83
+ # returns a true value. If no block is provided, the registry is
84
+ # always cleared at the end of a request.
85
+
86
+ def clear_registry(reg_, &condition_)
87
+ @tasks << RegistryMiddleware::ClearRegistry.new(reg_, &condition_)
88
+ self
89
+ end
90
+
91
+
92
+ # Set up the middleware to spawn a new registry on each request,
93
+ # using the given locked configuration as a template. The new
94
+ # registry is stored in the Rack environment with the given key.
95
+ # It is cleaned and disposed at the end of the request.
96
+
97
+ def spawn_registry(template_, envkey_)
98
+ @tasks << RegistryMiddleware::SpawnRegistry.new(template_, envkey_)
99
+ self
100
+ end
101
+
102
+
103
+ end
104
+
105
+
106
+ config.idregistry = Configuration.new
107
+
108
+
109
+ initializer :initialize_idregistry do |app_|
110
+ config_ = app_.config.idregistry
111
+ stack_ = app_.config.middleware
112
+ if (before_ = config_.before_middleware)
113
+ stack_.insert_before(before_, RegistryMiddleware, config_.tasks)
114
+ else
115
+ stack_.use(RegistryMiddleware, config_.tasks)
116
+ end
117
+ end
118
+
119
+
120
+ end
121
+
122
+
123
+ end
@@ -0,0 +1,580 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # IDRegistry registry object
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module IDRegistry
38
+
39
+
40
+ # A registry object.
41
+
42
+ class Registry
43
+
44
+
45
+ def initialize(patterns_, types_, categories_, methods_) # :nodoc:
46
+ @patterns = patterns_
47
+ @types = types_
48
+ @categories = categories_
49
+ @methods = methods_
50
+ @tuples = {}
51
+ @objects = {}
52
+ @catdata = {}
53
+ @config = Configuration._new(self, @patterns, @types, @categories, @methods)
54
+ @mutex = ::Mutex.new
55
+ end
56
+
57
+
58
+ def inspect # :nodoc:
59
+ "#<#{self.class}:0x#{object_id.to_s(16)} size=#{size}>"
60
+ end
61
+
62
+
63
+ # Get the configuration for this registry.
64
+ #
65
+ # You may also configure this registry by providing a block.
66
+ # The configuration object will then be available as a DSL.
67
+
68
+ def config(&block_)
69
+ ::Blockenspiel.invoke(block_, @config) if block_
70
+ @config
71
+ end
72
+
73
+
74
+ # Create a new empty registry, duplicating this registry's
75
+ # configuration.
76
+ #
77
+ # If the <tt>:unlocked</tt> option is set to true, the new registry
78
+ # will have an unlocked configuration that can be modified further.
79
+ # Otherwise, the new registry's configuration will be locked.
80
+ #
81
+ # Spawning a locked registry from a locked configuration is very fast
82
+ # because it reuses the configuration objects.
83
+
84
+ def spawn_registry(opts_={})
85
+ config.spawn_registry(opts_)
86
+ end
87
+
88
+
89
+ # Return the number of objects cached in the registry.
90
+
91
+ def size
92
+ @objects.size
93
+ end
94
+
95
+
96
+ # Retrieve the cached object corresponding to the given tuple. Returns
97
+ # nil if the object is not currently cached. Does not attempt to
98
+ # generate the object for you.
99
+
100
+ def get(tuple_)
101
+ objdata_ = @tuples[tuple_]
102
+ objdata_ ? objdata_[0] : nil
103
+ end
104
+
105
+
106
+ # Returns an array of all tuples corresponding to the given object,
107
+ # or the object identified by the given tuple.
108
+ # Returns nil if the given object is not cached in the registry.
109
+ #
110
+ # If you pass an Array, it is interpreted as a tuple.
111
+ # If you pass something other than an Array or a Hash, it is
112
+ # interpreted as an object.
113
+ # Otherwise, you can explicitly specify whether you are passing
114
+ # a tuple or object by using hash named arguments, e.g.
115
+ # <tt>:tuple =&gt;</tt>, or <tt>:object =&gt;</tt>.
116
+
117
+ def tuples_for(arg_)
118
+ objdata_ = _get_objdata(arg_)
119
+ objdata_ ? objdata_[2].keys : nil
120
+ end
121
+
122
+
123
+ # Returns true if the given object or tuple is present.
124
+ #
125
+ # If you pass an Array, it is interpreted as a tuple.
126
+ # If you pass something other than an Array or a Hash, it is
127
+ # interpreted as an object.
128
+ # Otherwise, you can explicitly specify whether you are passing
129
+ # a tuple or object by using hash named arguments, e.g.
130
+ # <tt>:tuple =&gt;</tt>, or <tt>:object =&gt;</tt>.
131
+
132
+ def include?(arg_)
133
+ _get_objdata(arg_) ? true : false
134
+ end
135
+
136
+
137
+ # Return all the categories for the given object or tuple.
138
+ #
139
+ # If you pass an Array, it is interpreted as a tuple.
140
+ # If you pass something other than an Array or a Hash, it is
141
+ # interpreted as an object.
142
+ # Otherwise, you can explicitly specify whether you are passing
143
+ # a tuple or object by using hash named arguments, e.g.
144
+ # <tt>:tuple =&gt;</tt>, or <tt>:object =&gt;</tt>.
145
+ #
146
+ # The return value is a hash. The keys are the category types
147
+ # relevant to this object. The values are the value arrays
148
+ # indicating which category the object falls under for each type.
149
+
150
+ def categories(arg_)
151
+ @config.lock
152
+
153
+ objdata_ = _get_objdata(arg_)
154
+ return nil unless objdata_
155
+ hash_ = {}
156
+ objdata_[2].each do |tup_, tupcats_|
157
+ tupcats_.each do |cat_|
158
+ hash_[cat_] = @categories[cat_][1].map{ |elem_| tup_[elem_] }
159
+ end
160
+ end
161
+ hash_
162
+ end
163
+
164
+
165
+ # Return all objects in a given category, which is specified by the
166
+ # category type and the value array indicating which category of that
167
+ # type.
168
+
169
+ def objects_in_category(category_type_, *category_spec_)
170
+ @config.lock
171
+
172
+ return nil unless @categories.include?(category_type_)
173
+ spec_ = category_spec_.size == 1 && category_spec_.first.is_a?(::Array) ? category_spec_.first : category_spec_
174
+ tuple_hash_ = (@catdata[category_type_] ||= {})[spec_]
175
+ tuple_hash_ ? tuple_hash_.values.map{ |objdata_| objdata_[0] } : []
176
+ end
177
+
178
+
179
+ # Return all tuples in a given category, which is specified by the
180
+ # category type and the value array indicating which category of that
181
+ # type.
182
+
183
+ def tuples_in_category(category_type_, *category_spec_)
184
+ @config.lock
185
+
186
+ return nil unless @categories.include?(category_type_)
187
+ spec_ = category_spec_.size == 1 && category_spec_.first.is_a?(::Array) ? category_spec_.first : category_spec_
188
+ tuple_hash_ = (@catdata[category_type_] ||= {})[spec_]
189
+ tuple_hash_ ? tuple_hash_.keys : []
190
+ end
191
+
192
+
193
+ # Get the object corresponding to the given tuple.
194
+ # If the tuple is not present, the registry tries to generate the
195
+ # object for you. Returns nil if it is unable to do so.
196
+ #
197
+ # You may pass the tuple as a single array argument, or as a set
198
+ # of arguments.
199
+ #
200
+ # If the last argument is a hash, it is removed from the tuple and
201
+ # treated as an options hash that may be passed to an object
202
+ # generator block.
203
+
204
+ def lookup(*args_)
205
+ opts_ = args_.last.is_a?(::Hash) ? args_.pop : {}
206
+ tuple_ = args_.size == 1 && args_.first.is_a?(::Array) ? args_.first : args_
207
+
208
+ @config.lock
209
+
210
+ # Fast-track lookup if it's already there
211
+ if (objdata_ = @tuples[tuple_])
212
+ return objdata_[0]
213
+ end
214
+
215
+ # Not there for now. Try to create the object.
216
+ # We want to do this before entering the synchronize block because
217
+ # we don't want callbacks called within the synchronization.
218
+ obj_ = nil
219
+ type_ = nil
220
+ pattern_ = nil
221
+ @patterns.each do |pat_, patdata_|
222
+ if Utils.matches?(pat_, tuple_)
223
+ block_ = patdata_[1]
224
+ obj_ = case block_.arity
225
+ when 0 then block_.call
226
+ when 1 then block_.call(tuple_)
227
+ when 2 then block_.call(tuple_, self)
228
+ else block_.call(tuple_, self, opts_)
229
+ end
230
+ unless obj_.nil?
231
+ pattern_ = pat_
232
+ type_ = patdata_[0]
233
+ break
234
+ end
235
+ end
236
+ end
237
+
238
+ if obj_
239
+ # Now attempt to insert the object.
240
+ # This part is synchronized to protect against concurrent mutation.
241
+ # Once in the synchronize block, we also double-check that no other
242
+ # thread added the object in the meantime. If another thread did,
243
+ # we throw away the object we just created, and return the other
244
+ # thread's object instead.
245
+ @mutex.synchronize do
246
+ if (objdata_ = @tuples[tuple_])
247
+ obj_ = objdata_[0]
248
+ else
249
+ _internal_add(type_, obj_, tuple_, pattern_)
250
+ end
251
+ end
252
+ end
253
+ obj_
254
+ end
255
+
256
+
257
+ # Add the given object to the registry. You must specify the type of
258
+ # object, which is used to determine what tuples correspond to it.
259
+
260
+ def add(type_, object_)
261
+ @config.lock
262
+
263
+ # Some sanity checks of the arguments.
264
+ if object_.nil?
265
+ raise ObjectKeyError, "Attempt to add a nil object"
266
+ end
267
+ unless @types.has_key?(type_)
268
+ raise ObjectKeyError, "Unrecognized type: #{type_}"
269
+ end
270
+
271
+ # Synchronize the actual add to protect against concurrent mutation.
272
+ @mutex.synchronize do
273
+ _internal_add(type_, object_, nil, nil)
274
+ end
275
+ self
276
+ end
277
+
278
+
279
+ # Delete the given object.
280
+ #
281
+ # If you pass an Array, it is interpreted as a tuple.
282
+ # If you pass something other than an Array or a Hash, it is
283
+ # interpreted as an object.
284
+ # Otherwise, you can explicitly specify whether you are passing
285
+ # a tuple or object by using hash named arguments, e.g.
286
+ # <tt>:tuple =&gt;</tt>, or <tt>:object =&gt;</tt>.
287
+
288
+ def delete(arg_)
289
+ @config.lock
290
+
291
+ @mutex.synchronize do
292
+ if (objdata_ = _get_objdata(arg_))
293
+ @objects.delete(objdata_[0].object_id)
294
+ objdata_[2].each_key{ |tup_| _remove_tuple(objdata_, tup_) }
295
+ end
296
+ end
297
+ self
298
+ end
299
+
300
+
301
+ # Delete all objects in a given category, which is specified by the
302
+ # category type and the value array indicating which category of that
303
+ # type.
304
+
305
+ def delete_category(category_type_, *category_spec_)
306
+ @config.lock
307
+
308
+ if @categories.include?(category_type_)
309
+ spec_ = category_spec_.size == 1 && category_spec_.first.is_a?(::Array) ? category_spec_.first : category_spec_
310
+ if (tuple_hash_ = (@catdata[category_type_] ||= {})[spec_])
311
+ @mutex.synchronize do
312
+ tuple_hash_.values.each do |objdata_|
313
+ @objects.delete(objdata_[0].object_id)
314
+ objdata_[2].each_key{ |tup_| _remove_tuple(objdata_, tup_) }
315
+ end
316
+ end
317
+ end
318
+ end
319
+ self
320
+ end
321
+
322
+
323
+ # Recompute the tuples for the given object, which may be identified
324
+ # by object or tuple. Call this when the value of the object changes
325
+ # in such a way that the registry should identify it differently.
326
+ #
327
+ # If you pass an Array, it is interpreted as a tuple.
328
+ # If you pass something other than an Array or a Hash, it is
329
+ # interpreted as an object.
330
+ # Otherwise, you can explicitly specify whether you are passing
331
+ # a tuple or object by using hash named arguments, e.g.
332
+ # <tt>:tuple =&gt;</tt>, or <tt>:object =&gt;</tt>.
333
+
334
+ def rekey(arg_)
335
+ @config.lock
336
+
337
+ # Resolve the object.
338
+ if (objdata_ = _get_objdata(arg_))
339
+
340
+ # Look up tuple generators from the type, and determine the
341
+ # new tuples for the object.
342
+ # Do this before entering the synchronize block because we
343
+ # don't want callbacks called within the synchronization.
344
+ obj_ = objdata_[0]
345
+ type_ = objdata_[1]
346
+ new_tuple_list_ = []
347
+ @types[type_].each do |pat_|
348
+ if (block_ = @patterns[pat_][2])
349
+ new_tuple_ = block_.call(obj_)
350
+ new_tuple_list_ << new_tuple_ if new_tuple_
351
+ else
352
+ raise ObjectKeyError, "Not all patterns for this type can generate tuples"
353
+ end
354
+ end
355
+
356
+ # Synchronize to protect against concurrent mutation.
357
+ @mutex.synchronize do
358
+ # One last check to ensure the object is still present
359
+ if @objects.has_key?(obj_.object_id)
360
+ # Ensure none of the new tuples isn't pointed elsewhere already.
361
+ # Tuples pointed at this object, ignore them.
362
+ # Tuples pointed at another object, raise an error.
363
+ tuple_hash_ = objdata_[2]
364
+ new_tuple_list_.delete_if do |tup_|
365
+ if tuple_hash_.has_key?(tup_)
366
+ true
367
+ elsif @tuples.has_key?(tup_)
368
+ raise ObjectKeyError, "Could not rekey because one of the new tuples is already present"
369
+ else
370
+ false
371
+ end
372
+ end
373
+ # Now go through and edit the tuples
374
+ (tuple_hash_.keys - new_tuple_list_).each do |tup_|
375
+ _remove_tuple(objdata_, tup_)
376
+ end
377
+ new_tuple_list_.each do |tup_|
378
+ _add_tuple(objdata_, tup_)
379
+ end
380
+ end
381
+ end
382
+ end
383
+ self
384
+ end
385
+
386
+
387
+ # Clear out all cached objects from the registry.
388
+
389
+ def clear
390
+ @mutex.synchronize do
391
+ @tuples.clear
392
+ @objects.clear
393
+ @catdata.clear
394
+ end
395
+ self
396
+ end
397
+
398
+
399
+ # Implement convenience methods.
400
+
401
+ def method_missing(name_, *args_) # :nodoc:
402
+ if (method_info_ = @methods[name_])
403
+ tuple_ = method_info_[0].dup
404
+ indexes_ = method_info_[1]
405
+ case indexes_
406
+ when ::Array
407
+ lookup_args_ = args_.size == indexes_.size + 1 ? args_.pop : {}
408
+ if lookup_args_.is_a?(::Hash) && args_.size == indexes_.size
409
+ args_.each_with_index do |a_, i_|
410
+ if (j_ = indexes_[i_])
411
+ tuple_[j_] = a_
412
+ end
413
+ end
414
+ return lookup(tuple_, lookup_args_)
415
+ end
416
+ when ::Hash
417
+ lookup_args_ = args_.size == 2 ? args_.pop : {}
418
+ if lookup_args_.is_a?(::Hash) && args_.size == 1
419
+ arg_ = args_[0]
420
+ if arg_.is_a?(::Hash)
421
+ arg_.each do |k_, v_|
422
+ if (j_ = indexes_[k_])
423
+ tuple_[j_] = v_
424
+ end
425
+ end
426
+ return lookup(tuple_, lookup_args_)
427
+ end
428
+ end
429
+ end
430
+ end
431
+ super
432
+ end
433
+
434
+
435
+ # Make sure respond_to does the right thing for convenience methods
436
+
437
+ def respond_to?(name_) # :nodoc:
438
+ super || @methods.include?(name_.to_sym)
439
+ end
440
+
441
+
442
+ # Internal method that gets an object data array given an object
443
+ # specification.
444
+
445
+ def _get_objdata(arg_) # :nodoc:
446
+ case arg_
447
+ when ::Array
448
+ @tuples[arg_]
449
+ when ::Hash
450
+ if (tuple_ = arg_[:tuple])
451
+ @tuples[tuple_]
452
+ elsif (obj_ = arg_[:object])
453
+ @objects[obj_.object_id]
454
+ else
455
+ nil
456
+ end
457
+ else
458
+ @objects[arg_.object_id]
459
+ end
460
+ end
461
+ private :_get_objdata
462
+
463
+
464
+ # Internal add method.
465
+ # This needs to be called within synchronization.
466
+ # The tuple and pattern arguments are for cases where we are adding
467
+ # because of a specific tuple lookup. We use that tuple for that
468
+ # pattern if the configuration doesn't generate a different tuple.
469
+
470
+ def _internal_add(type_, obj_, tuple_, pattern_) # :nodoc:
471
+ # Check if this object is present already.
472
+ if (objdata_ = @objects[obj_.object_id])
473
+ # The object is present already. If it has the right type,
474
+ # then just return the object and don't regenerate tuples.
475
+ # If it has the wrong type, give up.
476
+ if objdata_[1] != type_
477
+ raise ObjectKeyError, "Object is already present with type #{objdata_[1]}"
478
+ end
479
+ true
480
+ else
481
+ # Object is not present.
482
+ # Generate list of tuples to add, and make sure they are unique.
483
+ tuple_list_ = []
484
+ @types[type_].each do |pat_|
485
+ if (block_ = @patterns[pat_][2])
486
+ tup_ = block_.call(obj_)
487
+ else
488
+ tup_ = nil
489
+ end
490
+ if !tup_ && pat_ == pattern_
491
+ tup_ = tuple_
492
+ end
493
+ if tup_
494
+ if @tuples.has_key?(tup_)
495
+ raise ObjectKeyError, "New object wants to overwrite an existing tuple: #{tup_.inspect}"
496
+ end
497
+ tuple_list_ << tup_
498
+ end
499
+ end
500
+ return false if tuple_list_.size == 0
501
+
502
+ # Insert the object. This is the actual mutation.
503
+ objdata_ = [obj_, type_, {}]
504
+ @objects[obj_.object_id] = objdata_
505
+ tuple_list_.each do |tup_|
506
+ _add_tuple(objdata_, tup_) if tup_
507
+ end
508
+ true
509
+ end
510
+ end
511
+ private :_internal_add
512
+
513
+
514
+ # This needs to be called within synchronization.
515
+
516
+ def _add_tuple(objdata_, tuple_) # :nodoc:
517
+ return false if @tuples.has_key?(tuple_)
518
+ @tuples[tuple_] = objdata_
519
+ tupcats_ = []
520
+ @categories.each do |category_, catdata_|
521
+ if Utils.matches?(catdata_[0], tuple_)
522
+ index_ = catdata_[1].map{ |i_| tuple_[i_] }
523
+ ((@catdata[category_] ||= {})[index_] ||= {})[tuple_] = objdata_
524
+ tupcats_ << category_
525
+ end
526
+ end
527
+ objdata_[2][tuple_] = tupcats_
528
+ true
529
+ end
530
+ private :_add_tuple
531
+
532
+
533
+ # This needs to be called within synchronization.
534
+
535
+ def _remove_tuple(objdata_, tuple_) # :nodoc:
536
+ tupcats_ = objdata_[2][tuple_]
537
+ return false unless tupcats_
538
+ @tuples.delete(tuple_)
539
+ tupcats_.each do |cat_|
540
+ index_ = @categories[cat_][1].map{ |i_| tuple_[i_] }
541
+ @catdata[cat_][index_].delete(tuple_)
542
+ end
543
+ objdata_[2].delete(tuple_)
544
+ true
545
+ end
546
+ private :_remove_tuple
547
+
548
+
549
+ class << self
550
+
551
+ # :stopdoc:
552
+ alias_method :_new, :new
553
+ private :new
554
+ # :startdoc:
555
+
556
+ end
557
+
558
+
559
+ end
560
+
561
+
562
+ class << self
563
+
564
+
565
+ # Create a new, empty registry with an empty configuration.
566
+ #
567
+ # If you pass a block, it will be used to configure the registry,
568
+ # as if you had passed it to the config method.
569
+
570
+ def create(&block_)
571
+ reg_ = Registry._new({}, {}, {}, {})
572
+ reg_.config(&block_) if block_
573
+ reg_
574
+ end
575
+
576
+
577
+ end
578
+
579
+
580
+ end