blockenspiel 0.2.0.1-java

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,140 @@
1
+ /*
2
+ -----------------------------------------------------------------------------
3
+
4
+ Blockenspiel unmixer (JRuby implementation)
5
+
6
+ -----------------------------------------------------------------------------
7
+ Copyright 2008-2009 Daniel Azuma
8
+
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ * Redistributions of source code must retain the above copyright notice,
15
+ this list of conditions and the following disclaimer.
16
+ * Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+ * Neither the name of the copyright holder, nor the names of any other
20
+ contributors to this software, may be used to endorse or promote products
21
+ derived from this software without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ POSSIBILITY OF SUCH DAMAGE.
34
+ -----------------------------------------------------------------------------
35
+ */
36
+
37
+
38
+ /*
39
+ This implementation based on Mixology,
40
+ written by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
41
+ http://rubyforge.org/projects/mixology
42
+ http://github.com/dan-manges/mixology/tree/master
43
+
44
+ It has been stripped down and modified for JRuby 1.2 compatibility.
45
+ */
46
+
47
+ import java.io.IOException;
48
+ import java.lang.reflect.InvocationTargetException;
49
+ import java.lang.reflect.Method;
50
+ import java.util.Iterator;
51
+ import java.util.List;
52
+ import java.util.ArrayList;
53
+
54
+ import org.jruby.Ruby;
55
+ import org.jruby.RubyArray;
56
+ import org.jruby.RubyClass;
57
+ import org.jruby.RubyModule;
58
+ import org.jruby.anno.JRubyMethod;
59
+ import org.jruby.IncludedModuleWrapper;
60
+ import org.jruby.runtime.Block;
61
+ import org.jruby.runtime.builtin.IRubyObject;
62
+ import org.jruby.exceptions.RaiseException;
63
+ import org.jruby.runtime.load.BasicLibraryService;
64
+
65
+
66
+ public class BlockenspielUnmixerService implements BasicLibraryService
67
+ {
68
+
69
+ public boolean basicLoad(final Ruby runtime) throws IOException
70
+ {
71
+ RubyModule blockenspielModule = runtime.getOrCreateModule("Blockenspiel");
72
+ RubyModule unmixerModule = runtime.defineModuleUnder("Unmixer", blockenspielModule);
73
+ unmixerModule.getSingletonClass().defineAnnotatedMethods(BlockenspielUnmixerService.class);
74
+ return true;
75
+ }
76
+
77
+
78
+ @JRubyMethod(name = "unmix", required = 2)
79
+ public synchronized static IRubyObject unmix(IRubyObject self, IRubyObject receiver, IRubyObject module, Block block)
80
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
81
+ {
82
+ RubyModule actualModule = (RubyModule)module;
83
+ RubyClass klass = receiver.getMetaClass();
84
+ while (klass != receiver.getMetaClass().getRealClass())
85
+ {
86
+ RubyClass superClass = klass.getSuperClass();
87
+ if (superClass != null && superClass.getNonIncludedClass() == actualModule)
88
+ {
89
+ if (actualModule.getSuperClass() != null &&
90
+ actualModule.getSuperClass() instanceof IncludedModuleWrapper)
91
+ {
92
+ removeNestedModule(superClass, actualModule);
93
+ }
94
+ setSuperClass(klass, superClass.getSuperClass());
95
+ invalidateCacheDescendants(klass);
96
+ }
97
+ klass = superClass;
98
+ }
99
+ return receiver;
100
+ }
101
+
102
+
103
+ protected synchronized static void removeNestedModule(RubyClass klass, RubyModule module)
104
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
105
+ {
106
+ if ((klass.getSuperClass() instanceof IncludedModuleWrapper) &&
107
+ ((IncludedModuleWrapper)klass.getSuperClass()).getNonIncludedClass() ==
108
+ ((IncludedModuleWrapper)module.getSuperClass()).getNonIncludedClass())
109
+ {
110
+ if (module.getSuperClass().getSuperClass() != null &&
111
+ module.getSuperClass() instanceof IncludedModuleWrapper)
112
+ {
113
+ removeNestedModule(klass.getSuperClass(), module.getSuperClass());
114
+ }
115
+ setSuperClass(klass, klass.getSuperClass().getSuperClass());
116
+ }
117
+ }
118
+
119
+
120
+ protected synchronized static void setSuperClass(RubyModule klass, RubyModule superClass)
121
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
122
+ {
123
+ Method method = RubyModule.class.getDeclaredMethod("setSuperClass",
124
+ new Class[] {RubyClass.class} );
125
+ method.setAccessible(true);
126
+ Object[] superClassArg = new Object[] { superClass };
127
+ method.invoke(klass, superClassArg);
128
+ }
129
+
130
+
131
+ protected synchronized static void invalidateCacheDescendants(RubyModule klass)
132
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
133
+ {
134
+ Method method = RubyModule.class.getDeclaredMethod("invalidateCacheDescendants", new Class[0]);
135
+ method.setAccessible(true);
136
+ method.invoke(klass, new Object[0]);
137
+ }
138
+
139
+ }
140
+
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile 'blockenspiel/unmixer'
@@ -0,0 +1,86 @@
1
+ /*
2
+ -----------------------------------------------------------------------------
3
+
4
+ Blockenspiel unmixer (MRI implementation)
5
+
6
+ -----------------------------------------------------------------------------
7
+ Copyright 2008-2009 Daniel Azuma
8
+
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ * Redistributions of source code must retain the above copyright notice,
15
+ this list of conditions and the following disclaimer.
16
+ * Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+ * Neither the name of the copyright holder, nor the names of any other
20
+ contributors to this software, may be used to endorse or promote products
21
+ derived from this software without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ POSSIBILITY OF SUCH DAMAGE.
34
+ -----------------------------------------------------------------------------
35
+ */
36
+
37
+
38
+ /*
39
+ This implementation based on Mixology,
40
+ written by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
41
+ http://rubyforge.org/projects/mixology
42
+ http://github.com/dan-manges/mixology/tree/master
43
+
44
+ It has been stripped down and modified for compatibility with Ruby 1.9.
45
+ */
46
+
47
+
48
+ #include "ruby.h"
49
+
50
+
51
+ #ifndef RCLASS_SUPER
52
+ #define RCLASS_SUPER(c) (RCLASS(c)->super)
53
+ #endif
54
+
55
+
56
+ static void remove_nested_module(VALUE klass, VALUE module) {
57
+ if (RBASIC(RCLASS_SUPER(klass))->klass == RBASIC(RCLASS_SUPER(module))->klass) {
58
+ if (RCLASS_SUPER(RCLASS_SUPER(module)) && BUILTIN_TYPE(RCLASS_SUPER(module)) == T_ICLASS) {
59
+ remove_nested_module(RCLASS_SUPER(klass), RCLASS_SUPER(module));
60
+ }
61
+ RCLASS_SUPER(klass) = RCLASS_SUPER(RCLASS_SUPER(klass));
62
+ }
63
+ }
64
+
65
+
66
+ static VALUE do_unmix(VALUE self, VALUE receiver, VALUE module) {
67
+ VALUE klass = RBASIC(receiver)->klass;
68
+ while (klass != rb_class_real(klass)) {
69
+ VALUE super = RCLASS_SUPER(klass);
70
+ if (BUILTIN_TYPE(super) == T_ICLASS && RBASIC(super)->klass == module) {
71
+ if (RCLASS_SUPER(module) && BUILTIN_TYPE(RCLASS_SUPER(module)) == T_ICLASS) {
72
+ remove_nested_module(super, module);
73
+ }
74
+ RCLASS_SUPER(klass) = RCLASS_SUPER(super);
75
+ rb_clear_cache();
76
+ }
77
+ klass = super;
78
+ }
79
+ return receiver;
80
+ }
81
+
82
+
83
+ void Init_unmixer() {
84
+ VALUE container = rb_singleton_class(rb_define_module_under(rb_define_module("Blockenspiel"), "Unmixer"));
85
+ rb_define_method(container, "unmix", do_unmix, 2);
86
+ }
@@ -0,0 +1,43 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Blockenspiel entry point
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2008 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
+ if RUBY_PLATFORM =~ /java/
38
+ require "blockenspiel_unmixer"
39
+ else
40
+ require "#{File.dirname(__FILE__)}/blockenspiel/unmixer"
41
+ end
42
+ require "#{File.dirname(__FILE__)}/blockenspiel/impl"
43
+ require "#{File.dirname(__FILE__)}/blockenspiel/version"
@@ -0,0 +1,670 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Blockenspiel implementation
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2008-2009 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 'thread'
38
+
39
+
40
+ # == Blockenspiel
41
+ #
42
+ # The Blockenspiel module provides a namespace for Blockenspiel, as well as
43
+ # the main entry point method "invoke".
44
+
45
+ module Blockenspiel
46
+
47
+
48
+ # Base exception for all exceptions raised by Blockenspiel
49
+
50
+ class BlockenspielError < RuntimeError
51
+ end
52
+
53
+
54
+ # This exception is rasied when attempting to use the <tt>:proxy</tt> or
55
+ # <tt>:mixin</tt> parameterless behavior with a target that does not have
56
+ # the DSL module included. It is an error made by the DSL implementor.
57
+
58
+ class DSLMissingError < BlockenspielError
59
+ end
60
+
61
+
62
+ # This exception is raised when the block provided does not take the
63
+ # expected number of parameters. It is an error made by the caller.
64
+
65
+ class BlockParameterError < BlockenspielError
66
+ end
67
+
68
+
69
+ # === DSL setup methods
70
+ #
71
+ # These class methods are available after you have included the
72
+ # Blockenspiel::DSL module.
73
+ #
74
+ # By default, a class that has DSL capability will automatically make
75
+ # all public methods available to parameterless blocks, except for the
76
+ # +initialize+ method, any methods whose names begin with an underscore,
77
+ # and any methods whose names end with an equals sign.
78
+ #
79
+ # If you want to change this behavior, use the directives defined here to
80
+ # control exactly which methods are available to parameterless blocks.
81
+
82
+ module DSLSetupMethods
83
+
84
+ # Called when DSLSetupMethods extends a class.
85
+ # This sets up the current class, and adds a hook that causes
86
+ # any subclass of the current class to also be set up.
87
+
88
+ # :stopdoc:
89
+ def self.extended(klass_)
90
+ unless klass_.instance_variable_defined?(:@_blockenspiel_module)
91
+ _setup_class(klass_)
92
+ def klass_.inherited(subklass_)
93
+ Blockenspiel::DSLSetupMethods._setup_class(subklass_)
94
+ super
95
+ end
96
+ end
97
+ end
98
+ # :startdoc:
99
+
100
+
101
+ # Set up a class.
102
+ # Creates a DSL module for this class, optionally delegating to the superclass's module.
103
+ # Also initializes the class's methods hash and active flag.
104
+
105
+ def self._setup_class(klass_) # :nodoc:
106
+ superclass_ = klass_.superclass
107
+ superclass_ = nil unless superclass_.respond_to?(:_get_blockenspiel_module)
108
+ mod_ = Module.new
109
+ if superclass_
110
+ mod_.module_eval do
111
+ include superclass_._get_blockenspiel_module
112
+ end
113
+ end
114
+ klass_.instance_variable_set(:@_blockenspiel_superclass, superclass_)
115
+ klass_.instance_variable_set(:@_blockenspiel_module, mod_)
116
+ klass_.instance_variable_set(:@_blockenspiel_methods, Hash.new)
117
+ klass_.instance_variable_set(:@_blockenspiel_active, nil)
118
+ end
119
+
120
+
121
+ # Hook called when a method is added.
122
+ # This automatically makes the method a DSL method according to the current setting.
123
+
124
+ def method_added(symbol_) # :nodoc:
125
+ if @_blockenspiel_active
126
+ dsl_method(symbol_)
127
+ elsif @_blockenspiel_active.nil?
128
+ if symbol_ != :initialize && symbol_.to_s !~ /^_/ && symbol_.to_s !~ /=$/
129
+ dsl_method(symbol_)
130
+ end
131
+ end
132
+ super
133
+ end
134
+
135
+
136
+ # Get this class's corresponding DSL module
137
+
138
+ def _get_blockenspiel_module # :nodoc:
139
+ @_blockenspiel_module
140
+ end
141
+
142
+
143
+ # Get information on the given DSL method name.
144
+ # Possible values are the name of the delegate method, false for method disabled,
145
+ # or nil for method never defined.
146
+
147
+ def _get_blockenspiel_delegate(name_) # :nodoc:
148
+ delegate_ = @_blockenspiel_methods[name_]
149
+ if delegate_.nil? && @_blockenspiel_superclass
150
+ @_blockenspiel_superclass._get_blockenspiel_delegate(name_)
151
+ else
152
+ delegate_
153
+ end
154
+ end
155
+
156
+
157
+ # Make a particular method available to parameterless DSL blocks.
158
+ #
159
+ # To explicitly make a method available to parameterless blocks:
160
+ # dsl_method :my_method
161
+ #
162
+ # To explicitly exclude a method from parameterless blocks:
163
+ # dsl_method :my_method, false
164
+ #
165
+ # To explicitly make a method available to parameterless blocks, but
166
+ # point it to a method of a different name on the target class:
167
+ # dsl_method :my_method, :target_class_method
168
+
169
+ def dsl_method(name_, delegate_=nil)
170
+ name_ = name_.to_sym
171
+ if delegate_
172
+ delegate_ = delegate_.to_sym
173
+ elsif delegate_.nil?
174
+ delegate_ = name_
175
+ end
176
+ @_blockenspiel_methods[name_] = delegate_
177
+ unless @_blockenspiel_module.public_method_defined?(name_)
178
+ @_blockenspiel_module.module_eval("
179
+ def #{name_}(*params_, &block_)
180
+ val_ = Blockenspiel._target_dispatch(self, :#{name_}, params_, block_)
181
+ val_ == Blockenspiel::TARGET_MISMATCH ? super(*params_, &block_) : val_
182
+ end
183
+ ")
184
+ end
185
+ end
186
+
187
+
188
+ # Control the behavior of methods with respect to parameterless blocks,
189
+ # or make a list of methods available to parameterless blocks in bulk.
190
+ #
191
+ # To enable automatic exporting of methods to parameterless blocks.
192
+ # After executing this command, all public methods defined in the class
193
+ # will be available on parameterless blocks, until
194
+ # <tt>dsl_methods false</tt> is called:
195
+ # dsl_methods true
196
+ #
197
+ # To disable automatic exporting of methods to parameterless blocks.
198
+ # After executing this command, methods defined in this class will be
199
+ # excluded from parameterless blocks, until <tt>dsl_methods true</tt>
200
+ # is called:
201
+ # dsl_methods false
202
+ #
203
+ # To make a list of methods available to parameterless blocks in bulk:
204
+ # dsl_methods :my_method1, :my_method2, ...
205
+ #
206
+ # You can also point dsl methods to a method of a different name on the
207
+ # target class, by using a hash syntax, as follows:
208
+ # dsl_methods :my_method1 => :target_class_method1,
209
+ # :my_method2 => :target_class_method2
210
+ #
211
+ # You can mix non-renamed and renamed method declarations as long as
212
+ # the renamed (hash) methods are at the end. e.g.:
213
+ # dsl_methods :my_method1, :my_method2 => :target_class_method2
214
+
215
+ def dsl_methods(*names_)
216
+ if names_.size == 0 || names_ == [true]
217
+ @_blockenspiel_active = true
218
+ elsif names_ == [false]
219
+ @_blockenspiel_active = false
220
+ else
221
+ if names_.last.kind_of?(Hash)
222
+ names_.pop.each do |name_, delegate_|
223
+ dsl_method(name_, delegate_)
224
+ end
225
+ end
226
+ names_.each do |name_|
227
+ dsl_method(name_, name_)
228
+ end
229
+ end
230
+ end
231
+
232
+ end
233
+
234
+
235
+ # === DSL activation module
236
+ #
237
+ # Include this module in a class to mark this class as a DSL class and
238
+ # make it possible for its methods to be called from a block that does not
239
+ # take a parameter.
240
+ #
241
+ # After you include this module, you can use the directives defined in
242
+ # DSLSetupMethods to control what methods are available to DSL blocks
243
+ # that do not take parameters.
244
+
245
+ module DSL
246
+
247
+ def self.included(klass_) # :nodoc:
248
+ klass_.extend(Blockenspiel::DSLSetupMethods)
249
+ end
250
+
251
+ end
252
+
253
+
254
+ # === DSL activation base class
255
+ #
256
+ # Subclasses of this base class are considered DSL classes.
257
+ # Methods of the class can be made available to be called from a block that
258
+ # doesn't take an explicit block parameter.
259
+ # You may use the directives defined in DSLSetupMethods to control how
260
+ # methods of the class are handled in such blocks.
261
+ #
262
+ # Subclassing this base class is functionally equivalent to simply
263
+ # including Blockenspiel::DSL in the class.
264
+
265
+ class Base
266
+
267
+ include Blockenspiel::DSL
268
+
269
+ end
270
+
271
+
272
+ # Class for proxy delegators.
273
+ # The proxy behavior creates one of these delegators, mixes in the dsl
274
+ # methods, and uses instance_eval to invoke the block. This class delegates
275
+ # non-handled methods to the context object.
276
+
277
+ class ProxyDelegator # :nodoc:
278
+
279
+ def method_missing(symbol_, *params_, &block_)
280
+ Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
281
+ end
282
+
283
+ end
284
+
285
+
286
+ # === Dynamically construct a target
287
+ #
288
+ # These methods are available in a block passed to Blockenspiel#invoke and
289
+ # can be used to dynamically define what methods are available from a block.
290
+ # See Blockenspiel#invoke for more information.
291
+
292
+ class Builder
293
+
294
+ include Blockenspiel::DSL
295
+
296
+
297
+ # This is a base class for dynamically constructed targets.
298
+ # The actual target class is an anonymous subclass of this base class.
299
+
300
+ class Target # :nodoc:
301
+
302
+ include Blockenspiel::DSL
303
+
304
+
305
+ # Add a method specification to the subclass.
306
+
307
+ def self._add_methodinfo(name_, block_, yields_)
308
+ (@_blockenspiel_methodinfo ||= Hash.new)[name_] = [block_, yields_]
309
+ module_eval("
310
+ def #{name_}(*params_, &block_)
311
+ self.class._invoke_methodinfo(:#{name_}, params_, block_)
312
+ end
313
+ ")
314
+ end
315
+
316
+
317
+ # Attempt to invoke the given method on the subclass.
318
+
319
+ def self._invoke_methodinfo(name_, params_, block_)
320
+ info_ = @_blockenspiel_methodinfo[name_]
321
+ case info_[1]
322
+ when :first
323
+ params_.unshift(block_)
324
+ when :last
325
+ params_.push(block_)
326
+ end
327
+ info_[0].call(*params_)
328
+ end
329
+
330
+ end
331
+
332
+
333
+ # Sets up the dynamic target class.
334
+
335
+ def initialize # :nodoc:
336
+ @target_class = Class.new(Blockenspiel::Builder::Target)
337
+ @target_class.dsl_methods(false)
338
+ end
339
+
340
+
341
+ # Creates a new instance of the dynamic target class
342
+
343
+ def _create_target # :nodoc:
344
+ @target_class.new
345
+ end
346
+
347
+
348
+ # === Declare a DSL method.
349
+ #
350
+ # This call creates a method that can be called from the DSL block.
351
+ # Provide a name for the method, and a block defining the method's
352
+ # implementation. You may also provided a list of options as follows:
353
+ #
354
+ # The <tt>:block</tt> option controls how the generated method reports
355
+ # any block provided by the caller. If set to +false+ or not given, any
356
+ # caller-provided block is ignored. If set to <tt>:first</tt>, the
357
+ # block is _prepended_ (as a +Proc+ object) to the parameters passed to
358
+ # the method's implementing block. If set to <tt>:last</tt>, the block
359
+ # is _appended_. In either case, if the caller does not provide a block,
360
+ # a value of +nil+ is pre- or appended to the parameter list. A value of
361
+ # +true+ is equivalent to <tt>:first</tt>.
362
+ # (This is a workaround for the fact that blocks cannot themselves take
363
+ # block parameters in Ruby 1.8.)
364
+ #
365
+ # For example, to create a method named "foo" that takes one parameter
366
+ # and a block, do this:
367
+ #
368
+ # add_method(:foo, :block => :first) do |block, param|
369
+ # puts "foo called with parameter "+param.inspect
370
+ # puts "the block returned "+block.call.inspect
371
+ # end
372
+ #
373
+ # In your DSL, you can then call:
374
+ #
375
+ # foo("hello"){ "a value" }
376
+ #
377
+ # By default, a method of the same name is also made available to
378
+ # parameterless blocks. To change the name of the parameterless method,
379
+ # provide its name as the value of the <tt>:dsl_method</tt> option.
380
+ # To disable this method for parameterless blocks, set the
381
+ # <tt>:dsl_method</tt> option to +false+.
382
+ #
383
+ # For historical reasons, the <tt>:mixin</tt> option is an alias for
384
+ # <tt>:dsl_method</tt>. However, its use is deprecated.
385
+ #
386
+ # The <tt>:receive_block</tt> option is also supported for historical
387
+ # reasons, but its use is deprecated. Setting <tt>:receive_block</tt> to
388
+ # +true+ is equivalent to setting <tt>:block</tt> to <tt>:last</tt>.
389
+
390
+ def add_method(name_, opts_={}, &block_)
391
+ receive_block_ = opts_[:receive_block] ? :last : opts_[:block]
392
+ receive_block_ = :first if receive_block_ && receive_block_ != :last
393
+ @target_class._add_methodinfo(name_, block_, receive_block_)
394
+ dsl_method_name_ = opts_[:dsl_method] || opts_[:mixin]
395
+ if dsl_method_name_ != false
396
+ dsl_method_name_ = name_ if dsl_method_name_.nil? || dsl_method_name_ == true
397
+ @target_class.dsl_method(dsl_method_name_, name_)
398
+ end
399
+ end
400
+
401
+ end
402
+
403
+
404
+ # :stopdoc:
405
+ TARGET_MISMATCH = Object.new
406
+ # :startdoc:
407
+
408
+ @_target_stacks = Hash.new
409
+ @_mixin_counts = Hash.new
410
+ @_proxy_delegators = Hash.new
411
+ @_mutex = Mutex.new
412
+
413
+
414
+ # === Invoke a given block.
415
+ #
416
+ # This is the meat of Blockenspiel. Call this function to invoke a block
417
+ # provided by the user of your API.
418
+ #
419
+ # Normally, this method will check the block's arity to see whether it
420
+ # takes a parameter. If so, it will pass the given target to the block.
421
+ # If the block takes no parameter, and the given target is an instance of
422
+ # a class with DSL capability, the DSL methods are made available on the
423
+ # caller's self object so they may be called without a block parameter.
424
+ #
425
+ # Recognized options include:
426
+ #
427
+ # <tt>:parameterless</tt>::
428
+ # If set to false, disables parameterless blocks and always attempts to
429
+ # pass a parameter to the block. Otherwise, you may set it to one of
430
+ # three behaviors for parameterless blocks: <tt>:mixin</tt> (the
431
+ # default), <tt>:instance</tt>, and <tt>:proxy</tt>. See below for
432
+ # detailed descriptions of these behaviors.
433
+ # <tt>:parameter</tt>::
434
+ # If set to false, disables blocks with parameters, and always attempts
435
+ # to use parameterless blocks. Default is true, enabling parameter mode.
436
+ #
437
+ # The following values control the precise behavior of parameterless
438
+ # blocks. These are values for the <tt>:parameterless</tt> option.
439
+ #
440
+ # <tt>:mixin</tt>::
441
+ # This is the default behavior. DSL methods from the target are
442
+ # temporarily overlayed on the caller's +self+ object, but +self+ still
443
+ # points to the same object, so the helper methods and instance
444
+ # variables from the caller's closure remain available. The DSL methods
445
+ # are removed when the block completes.
446
+ # <tt>:instance</tt>::
447
+ # This behavior actually changes +self+ to the target object using
448
+ # <tt>instance_eval</tt>. Thus, the caller loses access to its own
449
+ # helper methods and instance variables, and instead gains access to the
450
+ # target object's instance variables. The target object's methods are
451
+ # not modified: this behavior does not apply any DSL method changes
452
+ # specified using <tt>dsl_method</tt> directives.
453
+ # <tt>:proxy</tt>::
454
+ # This behavior changes +self+ to a proxy object created by applying the
455
+ # DSL methods to an empty object, whose <tt>method_missing</tt> points
456
+ # back at the block's context. This behavior is a compromise between
457
+ # instance and mixin. As with instance, +self+ is changed, so the caller
458
+ # loses access to its own instance variables. However, the caller's own
459
+ # methods should still be available since any methods not handled by the
460
+ # DSL are delegated back to the caller. Also, as with mixin, the target
461
+ # object's instance variables are not available (and thus cannot be
462
+ # clobbered) in the block, and the transformations specified by
463
+ # <tt>dsl_method</tt> directives are honored.
464
+ #
465
+ # === Dynamic target generation
466
+ #
467
+ # It is also possible to dynamically generate a target object by passing
468
+ # a block to this method. This is probably best illustrated by example:
469
+ #
470
+ # Blockenspiel.invoke(block) do
471
+ # add_method(:set_foo) do |value|
472
+ # my_foo = value
473
+ # end
474
+ # add_method(:set_things_from_block, :receive_block => true) do |value,blk|
475
+ # my_foo = value
476
+ # my_bar = blk.call
477
+ # end
478
+ # end
479
+ #
480
+ # The above is roughly equivalent to invoking Blockenspiel with an
481
+ # instance of this target class:
482
+ #
483
+ # class MyFooTarget
484
+ # include Blockenspiel::DSL
485
+ # def set_foo(value)
486
+ # set_my_foo_from(value)
487
+ # end
488
+ # def set_things_from_block(value)
489
+ # set_my_foo_from(value)
490
+ # set_my_bar_from(yield)
491
+ # end
492
+ # end
493
+ #
494
+ # Blockenspiel.invoke(block, MyFooTarget.new)
495
+ #
496
+ # The obvious advantage of using dynamic object generation is that you are
497
+ # creating methods using closures, which provides the opportunity to, for
498
+ # example, modify closure variables such as my_foo. This is more difficult
499
+ # to do when you create a target class since its methods do not have access
500
+ # to outside data. Hence, in the above example, we hand-waved, assuming the
501
+ # existence of some method called "set_my_foo_from".
502
+ #
503
+ # The disadvantage is performance. If you dynamically generate a target
504
+ # object, it involves parsing and creating a new class whenever it is
505
+ # invoked. Thus, it is recommended that you use this technique for calls
506
+ # that are not used repeatedly, such as one-time configuration.
507
+ #
508
+ # See the Blockenspiel::Builder class for more details on add_method.
509
+ #
510
+ # (And yes, you guessed it: this API is a DSL block, and is itself
511
+ # implemented using Blockenspiel.)
512
+
513
+ def self.invoke(block_, target_=nil, opts_={}, &builder_block_)
514
+
515
+ unless block_
516
+ raise ArgumentError, "Block expected"
517
+ end
518
+ parameter_ = opts_[:parameter]
519
+ parameterless_ = opts_[:parameterless]
520
+
521
+ # Handle no-target behavior
522
+ if parameter_ == false && parameterless_ == false
523
+ if block_.arity != 0 && block_.arity != -1
524
+ raise Blockenspiel::BlockParameterError, "Block should not take parameters"
525
+ end
526
+ return block_.call
527
+ end
528
+
529
+ # Perform dynamic target generation if requested
530
+ if builder_block_
531
+ opts_ = target_ || opts_
532
+ builder_ = Blockenspiel::Builder.new
533
+ invoke(builder_block_, builder_)
534
+ target_ = builder_._create_target
535
+ end
536
+
537
+ # Handle parametered block case
538
+ if parameter_ != false && block_.arity == 1 || parameterless_ == false
539
+ if block_.arity != 1
540
+ raise Blockenspiel::BlockParameterError, "Block should take exactly one parameter"
541
+ end
542
+ return block_.call(target_)
543
+ end
544
+
545
+ # Check arity for parameterless case
546
+ if block_.arity != 0 && block_.arity != -1
547
+ raise Blockenspiel::BlockParameterError, "Block should not take parameters"
548
+ end
549
+
550
+ # Handle instance-eval behavior
551
+ if parameterless_ == :instance
552
+ return target_.instance_eval(&block_)
553
+ end
554
+
555
+ # Get the module of dsl methods
556
+ mod_ = target_.class._get_blockenspiel_module rescue nil
557
+ unless mod_
558
+ raise Blockenspiel::DSLMissingError
559
+ end
560
+
561
+ # Get the block's calling context object
562
+ object_ = Kernel.eval('self', block_.binding)
563
+
564
+ # Handle proxy behavior
565
+ if parameterless_ == :proxy
566
+
567
+ # Create proxy object
568
+ proxy_ = Blockenspiel::ProxyDelegator.new
569
+ proxy_.extend(mod_)
570
+
571
+ # Store the target and proxy object so dispatchers can get them
572
+ proxy_delegator_key_ = proxy_.object_id
573
+ target_stack_key_ = [Thread.current.object_id, proxy_.object_id]
574
+ @_proxy_delegators[proxy_delegator_key_] = object_
575
+ @_target_stacks[target_stack_key_] = [target_]
576
+
577
+ begin
578
+
579
+ # Call the block with the proxy as self
580
+ return proxy_.instance_eval(&block_)
581
+
582
+ ensure
583
+
584
+ # Clean up the dispatcher information
585
+ @_proxy_delegators.delete(proxy_delegator_key_)
586
+ @_target_stacks.delete(target_stack_key_)
587
+
588
+ end
589
+
590
+ end
591
+
592
+ # Handle mixin behavior (default)
593
+
594
+ # Create hash keys
595
+ mixin_count_key_ = [object_.object_id, mod_.object_id]
596
+ target_stack_key_ = [Thread.current.object_id, object_.object_id]
597
+
598
+ # Store the target for inheriting.
599
+ # We maintain a target call stack per thread.
600
+ target_stack_ = @_target_stacks[target_stack_key_] ||= Array.new
601
+ target_stack_.push(target_)
602
+
603
+ # Mix this module into the object, if required.
604
+ # This ensures that we keep track of the number of requests to
605
+ # mix this module in, from nested blocks and possibly multiple threads.
606
+ @_mutex.synchronize do
607
+ count_ = @_mixin_counts[mixin_count_key_]
608
+ if count_
609
+ @_mixin_counts[mixin_count_key_] = count_ + 1
610
+ else
611
+ @_mixin_counts[mixin_count_key_] = 1
612
+ object_.extend(mod_)
613
+ end
614
+ end
615
+
616
+ begin
617
+
618
+ # Now call the block
619
+ return block_.call
620
+
621
+ ensure
622
+
623
+ # Clean up the target stack
624
+ target_stack_.pop
625
+ @_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
626
+
627
+ # Remove the mixin from the object, if required.
628
+ @_mutex.synchronize do
629
+ count_ = @_mixin_counts[mixin_count_key_]
630
+ if count_ == 1
631
+ @_mixin_counts.delete(mixin_count_key_)
632
+ Blockenspiel::Unmixer.unmix(object_, mod_)
633
+ else
634
+ @_mixin_counts[mixin_count_key_] = count_ - 1
635
+ end
636
+ end
637
+
638
+ end
639
+
640
+ end
641
+
642
+
643
+ # This implements the mapping between DSL module methods and target object methods.
644
+ # We look up the current target object based on the current thread.
645
+ # Then we attempt to call the given method on that object.
646
+ # If we can't find an appropriate method to call, return the special value TARGET_MISMATCH.
647
+
648
+ def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
649
+ target_stack_ = @_target_stacks[[Thread.current.object_id, object_.object_id]]
650
+ return Blockenspiel::TARGET_MISMATCH unless target_stack_
651
+ target_stack_.reverse_each do |target_|
652
+ target_class_ = target_.class
653
+ delegate_ = target_class_._get_blockenspiel_delegate(name_)
654
+ if delegate_ && target_class_.public_method_defined?(delegate_)
655
+ return target_.send(delegate_, *params_, &block_)
656
+ end
657
+ end
658
+ return Blockenspiel::TARGET_MISMATCH
659
+ end
660
+
661
+
662
+ # This implements the proxy fall-back behavior.
663
+ # We look up the context object, and call the given method on that object.
664
+
665
+ def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
666
+ @_proxy_delegators[proxy_.object_id].send(name_, *params_, &block_)
667
+ end
668
+
669
+
670
+ end