blockenspiel 0.2.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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