idregistry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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