pluggability 0.0.1

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 ADDED
@@ -0,0 +1,4 @@
1
+ == v0.0.1 [2012-08-03] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ First release after renaming from PluginFactory.
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,8 @@
1
+ ChangeLog
2
+ History.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/pluggability.rb
7
+ spec/lib/helpers.rb
8
+ spec/pluggability_spec.rb
data/README.rdoc ADDED
@@ -0,0 +1,237 @@
1
+ = pluggability
2
+
3
+ * http://deveiate.org/projects/Pluggability
4
+
5
+
6
+ == Description
7
+
8
+ Pluggability is a mixin module that turns an including class into a factory for
9
+ its derivatives, capable of searching for and loading them by name. This is
10
+ useful when you have an abstract base class which defines an interface and basic
11
+ functionality for a part of a larger system, and a collection of subclasses
12
+ which implement the interface for different underlying functionality.
13
+
14
+ An example of where this might be useful is in a program which talks to a
15
+ database. To avoid coupling it to a specific database, you use a Driver class
16
+ which encapsulates your program's interaction with the database behind a useful
17
+ interface. Now you can create a concrete implementation of the Driver class for
18
+ each kind of database you wish to talk to. If you make the base Driver class a
19
+ Pluggability, too, you can add new drivers simply by dropping them in a
20
+ directory and using the Driver's `create` method to instantiate them:
21
+
22
+ === Synopsis
23
+
24
+ in driver.rb:
25
+
26
+ require "Pluggability"
27
+
28
+ class Driver
29
+ include Pluggability
30
+ def self::derivative_dirs
31
+ ["drivers"]
32
+ end
33
+ end
34
+
35
+ in drivers/mysql.rb:
36
+
37
+ require 'driver'
38
+
39
+ class MysqlDriver < Driver
40
+ ...implementation...
41
+ end
42
+
43
+ in /usr/lib/ruby/1.8/PostgresDriver.rb:
44
+
45
+ require 'driver'
46
+
47
+ class PostgresDriver < Driver
48
+ ...implementation...
49
+ end
50
+
51
+ elsewhere
52
+
53
+ require 'driver'
54
+
55
+ config[:driver_type] #=> "mysql"
56
+ driver = Driver.create( config[:driver_type] )
57
+ driver.class #=> MysqlDriver
58
+ pgdriver = Driver.create( "PostGresDriver" )
59
+
60
+ === How Plugins Are Loaded
61
+
62
+ The +create+ class method added to your class by Pluggability searches for your
63
+ module using several different strategies. It tries various permutations of the
64
+ base class's name in combination with the derivative requested. For example,
65
+ assume we want to make a +DataDriver+ base class, and then use plugins to define
66
+ drivers for different kinds of data sources:
67
+
68
+ require 'pluggability'
69
+
70
+ class DataDriver
71
+ include Pluggability
72
+ end
73
+
74
+ When you attempt to load the 'socket' data-driver class like so:
75
+
76
+ DataDriver.create( 'socket' )
77
+
78
+ Pluggability searches for modules with the following names:
79
+
80
+ 'socketdatadriver'
81
+ 'socket_datadriver'
82
+ 'socketDataDriver'
83
+ 'socket_DataDriver'
84
+ 'SocketDataDriver'
85
+ 'Socket_DataDriver'
86
+ 'socket'
87
+ 'Socket'
88
+
89
+ Obviously the last one will load something other than what is intended, so you
90
+ can also tell Pluggability that plugins should be loaded from a subdirectory by
91
+ declaring a class method called `derivative_dirs` in the base class. It should
92
+ return an Array that contains a list of subdirectories to try:
93
+
94
+ class DataDriver
95
+ include Pluggability
96
+
97
+ def self::derivative_dirs
98
+ ['drivers']
99
+ end
100
+ end
101
+
102
+ This will change the list that is required to:
103
+
104
+ 'drivers/socketdatadriver'
105
+ 'drivers/socket_datadriver'
106
+ 'drivers/socketDataDriver'
107
+ 'drivers/socket_DataDriver'
108
+ 'drivers/SocketDataDriver'
109
+ 'drivers/Socket_DataDriver'
110
+ 'drivers/socket'
111
+ 'drivers/Socket'
112
+
113
+ If you return more than one subdirectory, each of them will be tried in turn:
114
+
115
+ class DataDriver
116
+ include Pluggability
117
+
118
+ def self::derivative_dirs
119
+ ['drivers', 'datadriver']
120
+ end
121
+ end
122
+
123
+ will change the search to include:
124
+
125
+ 'drivers/socketdatadriver'
126
+ 'drivers/socket_datadriver'
127
+ 'drivers/socketDataDriver'
128
+ 'drivers/socket_DataDriver'
129
+ 'drivers/SocketDataDriver'
130
+ 'drivers/Socket_DataDriver'
131
+ 'drivers/socket'
132
+ 'drivers/Socket'
133
+ 'datadriver/socketdatadriver'
134
+ 'datadriver/socket_datadriver'
135
+ 'datadriver/socketDataDriver'
136
+ 'datadriver/socket_DataDriver'
137
+ 'datadriver/SocketDataDriver'
138
+ 'datadriver/Socket_DataDriver'
139
+ 'datadriver/socket'
140
+ 'datadriver/Socket'
141
+
142
+ If the plugin is not found, a FactoryError is raised, and the message will list
143
+ all the permutations that were tried.
144
+
145
+ === Logging
146
+
147
+ If you need a little more insight into what's going on, Pluggability uses
148
+ 'Logger' from the standard library. Just set its logger to your own to include
149
+ log messages about plugins being loaded:
150
+
151
+
152
+ require 'pluggability'
153
+ require 'logger'
154
+
155
+ class DataDriver
156
+ include Pluggability
157
+
158
+ end
159
+
160
+ $logger = Logger.new( $stderr )
161
+ $logger.level = Logger::DEBUG
162
+ Pluggability.logger = $logger
163
+
164
+ DataDriver.create( 'ringbuffer' )
165
+
166
+ this might generate a log that looks like:
167
+
168
+ D, [...] DEBUG -- : Loading derivative ringbuffer
169
+ D, [...] DEBUG -- : Subdirs are: [""]
170
+ D, [...] DEBUG -- : Path is: ["ringbufferdatadriver", "ringbufferDataDriver",
171
+ "ringbuffer"]...
172
+ D, [...] DEBUG -- : Trying ringbufferdatadriver...
173
+ D, [...] DEBUG -- : No module at 'ringbufferdatadriver', trying the next
174
+ alternative: 'no such file to load -- ringbufferdatadriver'
175
+ D, [...] DEBUG -- : Trying ringbufferDataDriver...
176
+ D, [...] DEBUG -- : No module at 'ringbufferDataDriver', trying the next
177
+ alternative: 'no such file to load -- ringbufferDataDriver'
178
+ D, [...] DEBUG -- : Trying ringbuffer...
179
+ D, [...] DEBUG -- : No module at 'ringbuffer', trying the next alternative:
180
+ 'no such file to load -- ringbuffer'
181
+ D, [...] DEBUG -- : fatals = []
182
+ E, [...] ERROR -- : Couldn't find a DataDriver named 'ringbuffer':
183
+ tried ["ringbufferdatadriver", "ringbufferDataDriver", "ringbuffer"]
184
+
185
+
186
+
187
+ == Installation
188
+
189
+ gem install pluggability
190
+
191
+
192
+ == Contributing
193
+
194
+ You can check out the current development source with Mercurial via its
195
+ {Mercurial repo}[http://repo.deveiate.org/Pluggability]. Or if you prefer
196
+ Git, via {its Github mirror}[https://github.com/ged/pluggability].
197
+
198
+ After checking out the source, run:
199
+
200
+ $ rake newb
201
+
202
+ This task will install any missing dependencies, run the tests/specs,
203
+ and generate the API documentation.
204
+
205
+
206
+ == License
207
+
208
+ Copyright (c) 2008-2012, Michael Granger and Martin Chase
209
+ All rights reserved.
210
+
211
+ Redistribution and use in source and binary forms, with or without
212
+ modification, are permitted provided that the following conditions are met:
213
+
214
+ * Redistributions of source code must retain the above copyright notice,
215
+ this list of conditions and the following disclaimer.
216
+
217
+ * Redistributions in binary form must reproduce the above copyright notice,
218
+ this list of conditions and the following disclaimer in the documentation
219
+ and/or other materials provided with the distribution.
220
+
221
+ * Neither the name of the author/s, nor the names of the project's
222
+ contributors may be used to endorse or promote products derived from this
223
+ software without specific prior written permission.
224
+
225
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
226
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
227
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
228
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
229
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
230
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
231
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
232
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
233
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
234
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
235
+
236
+
237
+
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'hoe'
4
+
5
+ Hoe.plugin :deveiate
6
+ Hoe.plugin :mercurial
7
+ Hoe.plugin :signing
8
+
9
+ Hoe.plugins.delete :rubyforge
10
+
11
+ hoespec = Hoe.spec 'pluggability' do
12
+ self.readme_file = 'README.rdoc'
13
+ self.history_file = 'History.rdoc'
14
+ self.extra_rdoc_files = Rake::FileList[ '*.rdoc' ]
15
+ self.spec_extras[:rdoc_options] = ['-f', 'fivefish', '-t', 'Pluggability Toolkit']
16
+
17
+ self.developer 'Martin Chase', 'stillflame@FaerieMUD.org'
18
+ self.developer 'Michael Granger', 'ged@FaerieMUD.org'
19
+
20
+ self.dependency 'loggability', '~> 0.5'
21
+
22
+ self.dependency 'hoe-deveiate', '~> 0.1', :development
23
+
24
+ self.spec_extras[:licenses] = ["BSD"]
25
+ self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
26
+ end
27
+
28
+ ENV['VERSION'] ||= hoespec.spec.version.to_s
29
+
30
+ task 'hg:precheckin' => [ :check_history, :check_manifest, :spec ]
31
+
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'loggability' unless defined?( Loggability )
4
+
5
+
6
+ # The Pluggability module
7
+ module Pluggability
8
+ extend Loggability
9
+
10
+ # Loggability API -- Set up a logger.
11
+ log_as :pluggability
12
+
13
+
14
+ # Library version
15
+ VERSION = '0.0.1'
16
+
17
+
18
+ ### An exception class for Pluggability specific errors.
19
+ class FactoryError < RuntimeError; end
20
+
21
+
22
+ ### Add the @derivatives instance variable to including classes.
23
+ def self::extend_object( obj )
24
+ obj.instance_variable_set( :@plugin_prefixes, [] )
25
+ obj.instance_variable_set( :@derivatives, {} )
26
+ super
27
+ end
28
+
29
+
30
+ #############################################################
31
+ ### M I X I N M E T H O D S
32
+ #############################################################
33
+
34
+ ### Get/set the prefixes that will be used when searching for particular plugins
35
+ ### for the calling Class.
36
+ def plugin_prefixes( *args )
37
+ @plugin_prefixes.replace( args ) if !args.empty?
38
+ return @plugin_prefixes
39
+ end
40
+
41
+
42
+ ### Set the prefixes that will be used when searching for particular plugins
43
+ ### for the calling Class.
44
+ def plugin_prefixes=( args )
45
+ @plugin_prefixes = Array( args )
46
+ end
47
+
48
+
49
+ ### Return the Hash of derivative classes, keyed by various versions of
50
+ ### the class name.
51
+ def derivatives
52
+ ancestors.each do |klass|
53
+ if klass.instance_variables.include?( :@derivatives ) ||
54
+ klass.instance_variables.include?( "@derivatives" )
55
+ return klass.instance_variable_get( :@derivatives )
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ ### Returns the type name used when searching for a derivative.
62
+ def factory_type
63
+ base = nil
64
+ self.ancestors.each do |klass|
65
+ if klass.instance_variables.include?( :@derivatives ) ||
66
+ klass.instance_variables.include?( "@derivatives" )
67
+ base = klass
68
+ break
69
+ end
70
+ end
71
+
72
+ raise FactoryError, "Couldn't find factory base for #{self.name}" if
73
+ base.nil?
74
+
75
+ if base.name =~ /^.*::(.*)/
76
+ return $1
77
+ else
78
+ return base.name
79
+ end
80
+ end
81
+
82
+
83
+ ### Inheritance callback -- Register subclasses in the derivatives hash
84
+ ### so that ::create knows about them.
85
+ def inherited( subclass )
86
+ keys = [ subclass ]
87
+
88
+ # If it's not an anonymous class, make some keys out of variants of its name
89
+ if subclass.name
90
+ simple_name = subclass.name.sub( /#<Class:0x[[:xdigit:]]+>::/i, '' )
91
+ keys << simple_name << simple_name.downcase
92
+
93
+ # Handle class names like 'FooBar' for 'Bar' factories.
94
+ Pluggability.log.debug "Inherited %p for %p-type plugins" % [ subclass, self.factory_type ]
95
+ if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factory_type})/i )
96
+ keys << Regexp.last_match[1].downcase
97
+ else
98
+ keys << subclass.name.sub( /.*::/, '' ).downcase
99
+ end
100
+ else
101
+ Pluggability.log.debug " no name-based variants for anonymous subclass %p" % [ subclass ]
102
+ end
103
+
104
+ keys.compact.uniq.each do |key|
105
+ Pluggability.log.info "Registering %s derivative of %s as %p" %
106
+ [ subclass.name, self.name, key ]
107
+ self.derivatives[ key ] = subclass
108
+ end
109
+
110
+ super
111
+ end
112
+
113
+
114
+ ### Returns an Array of registered derivatives
115
+ def derivative_classes
116
+ self.derivatives.values.uniq
117
+ end
118
+
119
+
120
+ ### Given the <tt>class_name</tt> of the class to instantiate, and other
121
+ ### arguments bound for the constructor of the new object, this method
122
+ ### loads the derivative class if it is not loaded already (raising a
123
+ ### LoadError if an appropriately-named file cannot be found), and
124
+ ### instantiates it with the given <tt>args</tt>. The <tt>class_name</tt>
125
+ ### may be the the fully qualified name of the class, the class object
126
+ ### itself, or the unique part of the class name. The following examples
127
+ ### would all try to load and instantiate a class called "FooListener"
128
+ ### if Listener included Factory
129
+ ### obj = Listener.create( 'FooListener' )
130
+ ### obj = Listener.create( FooListener )
131
+ ### obj = Listener.create( 'Foo' )
132
+ def create( class_name, *args, &block )
133
+ subclass = get_subclass( class_name )
134
+
135
+ begin
136
+ return subclass.new( *args, &block )
137
+ rescue => err
138
+ nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame}
139
+ msg = "When creating '#{class_name}': " + err.message
140
+ Kernel.raise( err, msg, nicetrace )
141
+ end
142
+ end
143
+
144
+
145
+ ### Given a <tt>class_name</tt> like that of the first argument to
146
+ ### #create, attempt to load the corresponding class if it is not
147
+ ### already loaded and return the class object.
148
+ def get_subclass( class_name )
149
+ return self if ( self.name == class_name || class_name == '' )
150
+ if class_name.is_a?( Class )
151
+ return class_name if class_name <= self
152
+ raise ArgumentError, "%s is not a descendent of %s" % [class_name, self]
153
+ end
154
+
155
+ class_name = class_name.to_s
156
+
157
+ # If the derivatives hash doesn't already contain the class, try to load it
158
+ unless self.derivatives.has_key?( class_name.downcase )
159
+ self.load_derivative( class_name )
160
+
161
+ subclass = self.derivatives[ class_name.downcase ]
162
+ unless subclass.is_a?( Class )
163
+ raise FactoryError,
164
+ "load_derivative(%s) added something other than a class "\
165
+ "to the registry for %s: %p" %
166
+ [ class_name, self.name, subclass ]
167
+ end
168
+ end
169
+
170
+ return self.derivatives[ class_name.downcase ]
171
+ end
172
+
173
+
174
+ ### Find and load all derivatives of this class, using plugin_prefixes if any
175
+ ### are defined, or a pattern derived from the #factory_type if not. Returns
176
+ ### an array of all derivative classes. Load failures are logged but otherwise
177
+ ### ignored.
178
+ def load_all
179
+ patterns = []
180
+ prefixes = self.plugin_prefixes
181
+
182
+ if prefixes && !prefixes.empty?
183
+ Pluggability.log.debug "Using plugin prefixes (%p) to build load patterns." % [ prefixes ]
184
+ prefixes.each do |prefix|
185
+ patterns << "#{prefix}/*.rb"
186
+ end
187
+ else
188
+ # Use all but the last pattern, which will just be '*.rb'
189
+ Pluggability.log.debug "Using factory type (%p) to build load patterns." %
190
+ [ self.factory_type ]
191
+ patterns += self.make_require_path( '*', '' )[0..-2].
192
+ map {|f| f + '.rb' }
193
+ end
194
+
195
+ patterns.each do |glob|
196
+ Pluggability.log.debug " finding derivatives matching pattern %p" % [ glob ]
197
+ candidates = Gem.find_files( glob )
198
+ Pluggability.log.debug " found %d matching files" % [ candidates.length ]
199
+ next if candidates.empty?
200
+
201
+ candidates.each {|path| require(path) }
202
+ end
203
+
204
+ return self.derivative_classes
205
+ end
206
+
207
+
208
+ ### Calculates an appropriate filename for the derived class using the
209
+ ### name of the base class and tries to load it via <tt>require</tt>. If
210
+ ### the including class responds to a method named
211
+ ### <tt>derivativeDirs</tt>, its return value (either a String, or an
212
+ ### array of Strings) is added to the list of prefix directories to try
213
+ ### when attempting to require a modules. Eg., if
214
+ ### <tt>class.derivativeDirs</tt> returns <tt>['foo','bar']</tt> the
215
+ ### require line is tried with both <tt>'foo/'</tt> and <tt>'bar/'</tt>
216
+ ### prepended to it.
217
+ def load_derivative( class_name )
218
+ Pluggability.log.debug "Loading derivative #{class_name}"
219
+
220
+ # Get the unique part of the derived class name and try to
221
+ # load it from one of the derivative subdirs, if there are
222
+ # any.
223
+ mod_name = self.get_module_name( class_name )
224
+ result = self.require_derivative( mod_name )
225
+
226
+ # Check to see if the specified listener is now loaded. If it
227
+ # is not, raise an error to that effect.
228
+ unless self.derivatives[ class_name.downcase ]
229
+ errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [
230
+ result,
231
+ self.factory_type,
232
+ class_name.downcase,
233
+ ]
234
+ Pluggability.log.error( errmsg )
235
+ raise FactoryError, errmsg, caller(3)
236
+ end
237
+ end
238
+
239
+
240
+ ### Build and return the unique part of the given <tt>class_name</tt>
241
+ ### either by stripping leading namespaces if the name already has the
242
+ ### name of the factory type in it (eg., 'My::FooService' for Service,
243
+ ### or by appending the factory type if it doesn't.
244
+ def get_module_name( class_name )
245
+ if class_name =~ /\w+#{self.factory_type}/
246
+ mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
247
+ else
248
+ mod_name = class_name
249
+ end
250
+
251
+ return mod_name
252
+ end
253
+
254
+
255
+ ### Search for the module with the specified <tt>mod_name</tt>, using any
256
+ ### #plugin_prefixes that have been set.
257
+ def require_derivative( mod_name )
258
+
259
+ subdirs = self.plugin_prefixes
260
+ subdirs << '' if subdirs.empty?
261
+ Pluggability.log.debug "Subdirs are: %p" % [subdirs]
262
+ fatals = []
263
+ tries = []
264
+
265
+ # Iterate over the subdirs until we successfully require a
266
+ # module.
267
+ subdirs.map( &:strip ).each do |subdir|
268
+ self.make_require_path( mod_name, subdir ).each do |path|
269
+ Pluggability.logger.debug "Trying #{path}..."
270
+ tries << path
271
+
272
+ # Try to require the module, saving errors and jumping
273
+ # out of the catch block on success.
274
+ begin
275
+ require( path.untaint )
276
+ rescue LoadError => err
277
+ Pluggability.log.debug "No module at '%s', trying the next alternative: '%s'" %
278
+ [ path, err.message ]
279
+ rescue Exception => err
280
+ fatals << err
281
+ Pluggability.log.error "Found '#{path}', but encountered an error: %s\n\t%s" %
282
+ [ err.message, err.backtrace.join("\n\t") ]
283
+ else
284
+ Pluggability.log.info "Loaded '#{path}' without error."
285
+ return path
286
+ end
287
+ end
288
+ end
289
+
290
+ Pluggability.logger.debug "fatals = %p" % [ fatals ]
291
+
292
+ # Re-raise is there was a file found, but it didn't load for
293
+ # some reason.
294
+ if fatals.empty?
295
+ errmsg = "Couldn't find a %s named '%s': tried %p" % [
296
+ self.factory_type,
297
+ mod_name,
298
+ tries
299
+ ]
300
+ Pluggability.log.error( errmsg )
301
+ raise FactoryError, errmsg
302
+ else
303
+ Pluggability.log.debug "Re-raising first fatal error"
304
+ Kernel.raise( fatals.first )
305
+ end
306
+ end
307
+
308
+
309
+ ### Make a list of permutations of the given +modname+ for the given
310
+ ### +subdir+. Called on a +DataDriver+ class with the arguments 'Socket' and
311
+ ### 'drivers', returns:
312
+ ### ["drivers/socketdatadriver", "drivers/socketDataDriver",
313
+ ### "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]
314
+ def make_require_path( modname, subdir )
315
+ path = []
316
+ myname = self.factory_type
317
+
318
+ # Make permutations of the two parts
319
+ path << modname
320
+ path << modname.downcase
321
+ path << modname + myname
322
+ path << modname.downcase + myname
323
+ path << modname.downcase + myname.downcase
324
+ path << modname + '_' + myname
325
+ path << modname.downcase + '_' + myname
326
+ path << modname.downcase + '_' + myname.downcase
327
+
328
+ # If a non-empty subdir was given, prepend it to all the items in the
329
+ # path
330
+ unless subdir.nil? or subdir.empty?
331
+ path.collect! {|m| File.join(subdir, m)}
332
+ end
333
+
334
+ Pluggability.log.debug "Path is: #{path.uniq.reverse.inspect}..."
335
+ return path.uniq.reverse
336
+ end
337
+
338
+ end # module Pluggability
339
+
340
+
341
+ # Backward-compatibility alias
342
+ FactoryError = Pluggability::FactoryError unless defined?( FactoryError )
343
+