Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / delegate.rb
blobd54d5fe487476d9149e4b2baa0b4821771d89b11
1 # = delegate -- Support for the Delegation Pattern
3 # Documentation by James Edward Gray II and Gavin Sinclair
5 # == Introduction
7 # This library provides three different ways to delegate method calls to an
8 # object.  The easiest to use is SimpleDelegator.  Pass an object to the
9 # constructor and all methods supported by the object will be delegated.  This
10 # object can be changed later.
12 # Going a step further, the top level DelegateClass method allows you to easily
13 # setup delegation through class inheritance.  This is considerably more
14 # flexible and thus probably the most common use for this library.
16 # Finally, if you need full control over the delegation scheme, you can inherit
17 # from the abstract class Delegator and customize as needed.  (If you find
18 # yourself needing this control, have a look at _forwardable_, also in the
19 # standard library.  It may suit your needs better.)
21 # == Notes
23 # Be advised, RDoc will not detect delegated methods.
25 # <b>delegate.rb provides full-class delegation via the
26 # DelegateClass() method.  For single-method delegation via
27 # def_delegator(), see forwardable.rb.</b>
29 # == Examples
31 # === SimpleDelegator
33 # Here's a simple example that takes advantage of the fact that
34 # SimpleDelegator's delegation object can be changed at any time.
36 #   class Stats
37 #     def initialize
38 #       @source = SimpleDelegator.new([])
39 #     end
40 #     
41 #     def stats( records )
42 #       @source.__setobj__(records)
43 #               
44 #       "Elements:  #{@source.size}\n" +
45 #       " Non-Nil:  #{@source.compact.size}\n" +
46 #       "  Unique:  #{@source.uniq.size}\n"
47 #     end
48 #   end
49 #   
50 #   s = Stats.new
51 #   puts s.stats(%w{James Edward Gray II})
52 #   puts
53 #   puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])
55 # <i>Prints:</i>
57 #   Elements:  4
58 #    Non-Nil:  4
59 #     Unique:  4
60
61 #   Elements:  8
62 #    Non-Nil:  7
63 #     Unique:  6
65 # === DelegateClass()
67 # Here's a sample of use from <i>tempfile.rb</i>.
69 # A _Tempfile_ object is really just a _File_ object with a few special rules
70 # about storage location and/or when the File should be deleted.  That makes for
71 # an almost textbook perfect example of how to use delegation.
73 #   class Tempfile < DelegateClass(File)
74 #     # constant and class member data initialization...
75 #   
76 #     def initialize(basename, tmpdir=Dir::tmpdir)
77 #       # build up file path/name in var tmpname...
78 #     
79 #       @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
80 #     
81 #       # ...
82 #     
83 #       super(@tmpfile)
84 #     
85 #       # below this point, all methods of File are supported...
86 #     end
87 #   
88 #     # ...
89 #   end
91 # === Delegator
93 # SimpleDelegator's implementation serves as a nice example here.
95 #    class SimpleDelegator < Delegator
96 #      def initialize(obj)
97 #        super             # pass obj to Delegator constructor, required
98 #        @_sd_obj = obj    # store obj for future use
99 #      end
101 #      def __getobj__
102 #        @_sd_obj          # return object we are delegating to, required
103 #      end
105 #      def __setobj__(obj)
106 #        @_sd_obj = obj    # change delegation object, a feature we're providing
107 #      end
109 #      # ...
110 #    end
113 # Delegator is an abstract class used to build delegator pattern objects from
114 # subclasses.  Subclasses should redefine \_\_getobj\_\_.  For a concrete
115 # implementation, see SimpleDelegator.
117 class Delegator
119   #
120   # Pass in the _obj_ to delegate method calls to.  All methods supported by
121   # _obj_ will be delegated to.
122   #
123   def initialize(obj)
124     preserved = ::Kernel.public_instance_methods(false)
125     preserved -= ["to_s","to_a","inspect","==","=~","==="]
126     for t in self.class.ancestors
127       preserved |= t.public_instance_methods(false)
128       preserved |= t.private_instance_methods(false)
129       preserved |= t.protected_instance_methods(false)
130       break if t == Delegator
131     end
132     preserved << "singleton_method_added"
133     for method in obj.methods
134       next if preserved.include? method
135       begin
136         eval <<-EOS
137         def self.#{method}(*args, &block)
138            __getobj__.__send__(:#{method}, *args, &block)
139         end
140         EOS
141       rescue SyntaxError
142         raise NameError, "invalid identifier %s" % method, caller(4)
143       end
144     end
145   end
146   alias initialize_methods initialize
148   # Handles the magic of delegation through \_\_getobj\_\_.
149   def method_missing(m, *args)
150     target = self.__getobj__
151     unless target.respond_to?(m)
152       super(m, *args)
153     end
154     target.__send__(m, *args)
155   end
157   # 
158   # Checks for a method provided by this the delegate object by fowarding the 
159   # call through \_\_getobj\_\_.
160   # 
161   def respond_to?(m)
162     return true if super
163     return self.__getobj__.respond_to?(m)
164   end
166   #
167   # This method must be overridden by subclasses and should return the object
168   # method calls are being delegated to.
169   #
170   def __getobj__
171     raise NotImplementedError, "need to define `__getobj__'"
172   end
174   # Serialization support for the object returned by \_\_getobj\_\_.
175   def marshal_dump
176     __getobj__
177   end
178   # Reinitializes delegation from a serialized object.
179   def marshal_load(obj)
180     initialize_methods(obj)
181     __setobj__(obj)
182   end
186 # A concrete implementation of Delegator, this class provides the means to
187 # delegate all supported method calls to the object passed into the constructor
188 # and even to change the object being delegated to at a later time with
189 # \_\_setobj\_\_ .
191 class SimpleDelegator<Delegator
193   # Pass in the _obj_ you would like to delegate method calls to.
194   def initialize(obj)
195     super
196     @_sd_obj = obj
197   end
199   # Returns the current object method calls are being delegated to.
200   def __getobj__
201     @_sd_obj
202   end
204   #
205   # Changes the delegate object to _obj_.
206   #
207   # It's important to note that this does *not* cause SimpleDelegator's methods
208   # to change.  Because of this, you probably only want to change delegation
209   # to objects of the same type as the original delegate.
210   #
211   # Here's an example of changing the delegation object.
212   #
213   #   names = SimpleDelegator.new(%w{James Edward Gray II})
214   #   puts names[1]    # => Edward
215   #   names.__setobj__(%w{Gavin Sinclair})
216   #   puts names[1]    # => Sinclair
217   #
218   def __setobj__(obj)
219     raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
220     @_sd_obj = obj
221   end
223   # Clone support for the object returned by \_\_getobj\_\_.
224   def clone
225     super
226     __setobj__(__getobj__.clone)
227   end
228   # Duplication support for the object returned by \_\_getobj\_\_.
229   def dup(obj)
230     super
231     __setobj__(__getobj__.dup)
232   end
235 # :stopdoc:
236 # backward compatibility ^_^;;;
237 Delegater = Delegator
238 SimpleDelegater = SimpleDelegator
239 # :startdoc:
242 # The primary interface to this library.  Use to setup delegation when defining
243 # your class.
245 #   class MyClass < DelegateClass( ClassToDelegateTo )    # Step 1
246 #     def initiaize
247 #       super(obj_of_ClassToDelegateTo)                   # Step 2
248 #     end
249 #   end
251 def DelegateClass(superclass)
252   klass = Class.new
253   methods = superclass.public_instance_methods(true)
254   methods -= ::Kernel.public_instance_methods(false)
255   methods -= %w[
256     __verify_metaclass__
257     become!
258     copy_from
259     metaclass
260     to_marshal
261   ]
262   methods |= ["to_s","to_a","inspect","==","=~","==="]
264   klass.module_eval do
265     def initialize(obj)  # :nodoc:
266       @_dc_obj = obj
267     end
269     def method_missing(m, *args)  # :nodoc:
270       unless @_dc_obj.respond_to?(m)
271         super(m, *args)
272       end
273       @_dc_obj.__send__(m, *args)
274     end
276     def respond_to?(m)  # :nodoc:
277       return true if super
278       return @_dc_obj.respond_to?(m)
279     end
281     def __getobj__  # :nodoc:
282       @_dc_obj
283     end
285     def __setobj__(obj)  # :nodoc:
286       raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
287       @_dc_obj = obj
288     end
290     def clone  # :nodoc:
291       super
292       __setobj__(__getobj__.clone)
293     end
295     def dup  # :nodoc:
296       super
297       __setobj__(__getobj__.dup)
298     end
299   end
301   methods.each do |method|
302     begin
303       klass.module_eval <<-EOS
304         def #{method}(*args, &block)
305           @_dc_obj.__send__(:#{method}, *args, &block)
306         end
307       EOS
308     rescue SyntaxError
309       raise NameError, "invalid identifier #{method}"
310     end
311   end
312   return klass
315 # :enddoc:
317 if __FILE__ == $0
318   class ExtArray<DelegateClass(Array)
319     def initialize()
320       super([])
321     end
322   end
324   ary = ExtArray.new
325   p ary.class
326   ary.push 25
327   p ary
329   foo = Object.new
330   def foo.test
331     25
332   end
333   def foo.error
334     raise 'this is OK'
335   end
336   foo2 = SimpleDelegator.new(foo)
337   p foo.test == foo2.test       # => true
338   foo2.error                    # raise error!