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.
- data/History.rdoc +3 -0
- data/IDRegistry.rdoc +180 -0
- data/README.rdoc +185 -0
- data/Version +1 -0
- data/lib/idregistry.rb +48 -0
- data/lib/idregistry/configuration.rb +523 -0
- data/lib/idregistry/errors.rb +66 -0
- data/lib/idregistry/middleware.rb +144 -0
- data/lib/idregistry/railtie.rb +123 -0
- data/lib/idregistry/registry.rb +580 -0
- data/lib/idregistry/utils.rb +64 -0
- data/lib/idregistry/version.rb +53 -0
- data/test/tc_categories.rb +153 -0
- data/test/tc_configuration.rb +330 -0
- data/test/tc_middleware.rb +139 -0
- data/test/tc_misc.rb +111 -0
- data/test/tc_simple_patterns.rb +221 -0
- data/test/tc_threads.rb +78 -0
- metadata +71 -0
@@ -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 =></tt>, or <tt>:object =></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 =></tt>, or <tt>:object =></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 =></tt>, or <tt>:object =></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 =></tt>, or <tt>:object =></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 =></tt>, or <tt>:object =></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
|