# Copyright: Copyright (c) 2004 Nicolas Despres. All rights reserved. # Author: Nicolas Despres . # License: Gnu General Public License. # $LastChangedBy: ertai $ # $Id: abstract.rb 210 2005-05-05 19:59:45Z ertai $ #FIXME: find a way to be thread safe. require 'module/instance_method_visibility' module Concrete; end # When this module is included into a class, this class can't be instantiate # any more until the Concrete module is included too. This module cannot be # included if the Concrete module has been included before in one of the # class' superclass. module Abstract def self.included(klass) super if klass.include?(Concrete) raise(TypeError, "#{klass} - cannot make abstract a concrete class") end klass.module_eval do class << self unless private_method_defined?(:concrete_new) alias_method :concrete_new, :new visibility = instance_method_visibility('new') def new(*args, &block) raise(TypeError, "cannot instantiate an abstract class #{name}") end send(visibility, :new) private :concrete_new end end end end end # module Abstract # Include this module in an abstract class to make it concrete and so, to be # able to instantiate it again. This module can be included if and only if the # Abstract module has been included before in one of the class' superclass. module Concrete def self.included(klass) super unless klass.include?(Abstract) raise(TypeError, "#{klass} - not an abstract class") end klass.module_eval do class << self visibility = instance_method_visibility('new') def new(*args, &block) concrete_new(*args, &block) end send(visibility, :new) end def is_a?(klass) klass == Abstract ? false : super(klass) end end end end # module Concrete class Class def abstract include Abstract end def concrete include Concrete end def abstract? include?(Abstract) ? (not include?(Concrete)) : false; end end # class Class if (not defined? ABSTRACT_TESTED) and (defined? TEST_MODE or __FILE__ == $0) ABSTRACT_TESTED = true require 'test/unit/ui/yaml/testrunner' require 'singleton' class AbstractTest < Test::Unit::TestCase class R; end class A include Abstract attr_reader :initialized def initialize(toto=nil) @initialized = true end end class B < A; end def test_abstract assert_nothing_raised { R.new } assert(A.include?(Abstract)) assert_raises(TypeError) { A.new } assert_raises(TypeError) { B.new } assert_raises(TypeError) do AbstractTest.module_eval %q{ class D include Concrete include Abstract end } end end class C < A include Concrete attr_reader :toto def initialize(toto=nil) super @toto = toto end end class CC < C; end def test_concrete assert_raises(TypeError) do AbstractTest.module_eval %q{ class D; include Concrete; end } end assert(C.new.initialized) assert_nil(C.new.toto) assert_equal('toto', C.new('toto').toto) assert(CC.new.initialized) assert_equal(SC.instance, SC.instance) assert_equal(SCC.instance, SCC.instance) assert_not_equal(SC.instance, SCC.instance) end class SA include Singleton include Abstract attr_reader :initialized def initialize(toto=nil) @initialized = true end end class SB < SA; end def test_abstract_singleton assert(SA.include?(Abstract)) assert_raises(NoMethodError) { SA.new } assert_raises(TypeError) { SA.instance } assert_raises(NoMethodError) { SB.new } assert_raises(TypeError) { SB.instance } assert_raises(TypeError) do AbstractTest.module_eval %q{ class SD; include Concrete; include Abstract; end } end end class SC < SA include Concrete end class SCC < SC; end def test_concrete_singleton assert_raises(TypeError) do AbstractTest.module_eval %q{ class SD; include Concrete; end } end assert_raises(NoMethodError) { SC.new } assert(SC.instance.initialized) assert(SCC.instance.initialized) assert_equal(SC.instance, SC.instance) assert_equal(SCC.instance, SCC.instance) assert_not_equal(SC.instance, SCC.instance) end class IsA; include Abstract; end class IsB < IsA; include Concrete; end def test_is_a assert(! IsB.new.is_a?(Abstract)) assert(IsB.new.is_a?(IsA)) assert(IsB.include?(Abstract)) end def test_abstract? assert(IsA.abstract?) assert(! IsB.abstract?) end class AArg include Abstract attr_reader :arg def initialize(arg) @arg = arg end end class CArg < AArg include Concrete end def test_arg assert_nothing_raised { CArg.new('toto') } assert_raises(ArgumentError) { CArg.new } end class AA_; include Abstract; end class BB_ < AA_; include Abstract; end class CC_ < BB_; include Concrete; end class DD_ < CC_; include Concrete; end def test_double_abstract assert_raises(TypeError) { AA_.new } assert_raises(TypeError) { BB_.new } assert_nothing_raised { CC_.new } assert_nothing_raised { DD_.new } end end # class AbstractTest end