stop the redefine warnings?
[gitmo/Class-C3.git] / lib / Class / C3.pm
index 988a8e9..8b4e75a 100644 (file)
@@ -4,10 +4,29 @@ package Class::C3;
 use strict;
 use warnings;
 
-use Scalar::Util 'blessed';
-use Algorithm::C3;
+our $VERSION = '0.19';
 
-our $VERSION = '0.12';
+our $C3_IN_CORE;
+our $C3_XS;
+
+BEGIN {
+    if($] > 5.009_004) {
+        $C3_IN_CORE = 1;
+        require mro;
+    }
+    else {
+        eval "require Class::C3::XS";
+        my $error = $@;
+        if(!$error) {
+            $C3_XS = 1;
+        }
+        else {
+            die $error if $error !~ /\blocate\b/;
+            require Algorithm::C3;
+            require Class::C3::next;
+        }
+    }
+}
 
 # this is our global stash of both 
 # MRO's and method dispatch tables
@@ -29,12 +48,18 @@ our %MRO;
 sub _dump_MRO_table { %MRO }
 our $TURN_OFF_C3 = 0;
 
+# state tracking for initialize()/uninitialize()
+our $_initialized = 0;
+
 sub import {
     my $class = caller();
     # skip if the caller is main::
     # since that is clearly not relevant
     return if $class eq 'main';
+
     return if $TURN_OFF_C3;
+    mro::set_mro($class, 'c3') if $C3_IN_CORE;
+
     # make a note to calculate $class 
     # during INIT phase
     $MRO{$class} = undef unless exists $MRO{$class};
@@ -42,42 +67,63 @@ sub import {
 
 ## initializers
 
+# This prevents silly warnings when Class::C3 is
+#  used explicitly along with MRO::Compat under 5.9.5+
+
+{ no warnings 'redefine';
+
 sub initialize {
+    %next::METHOD_CACHE = ();
     # why bother if we don't have anything ...
     return unless keys %MRO;
-    _calculate_method_dispatch_tables();
-    _apply_method_dispatch_tables();
-    %next::METHOD_CACHE = ();
+    if($C3_IN_CORE) {
+        mro::set_mro($_, 'c3') for keys %MRO;
+    }
+    else {
+        if($_initialized) {
+            uninitialize();
+            $MRO{$_} = undef foreach keys %MRO;
+        }
+        _calculate_method_dispatch_tables();
+        _apply_method_dispatch_tables();
+        $_initialized = 1;
+    }
 }
 
 sub uninitialize {
     # why bother if we don't have anything ...
-    return unless keys %MRO;    
-    _remove_method_dispatch_tables();    
     %next::METHOD_CACHE = ();
+    return unless keys %MRO;    
+    if($C3_IN_CORE) {
+        mro::set_mro($_, 'dfs') for keys %MRO;
+    }
+    else {
+        _remove_method_dispatch_tables();    
+        $_initialized = 0;
+    }
 }
 
-sub reinitialize {
-    uninitialize();
-    # clean up the %MRO before we re-initialize
-    $MRO{$_} = undef foreach keys %MRO;
-    initialize();
-}
+sub reinitialize { goto &initialize }
+
+} # end of "no warnings 'redefine'"
 
 ## functions for applying C3 to classes
 
 sub _calculate_method_dispatch_tables {
+    return if $C3_IN_CORE;
+    my %merge_cache;
     foreach my $class (keys %MRO) {
-        _calculate_method_dispatch_table($class);
+        _calculate_method_dispatch_table($class, \%merge_cache);
     }
 }
 
 sub _calculate_method_dispatch_table {
-    my $class = shift;
+    return if $C3_IN_CORE;
+    my ($class, $merge_cache) = @_;
     no strict 'refs';
-    my @MRO = calculateMRO($class);
+    my @MRO = calculateMRO($class, $merge_cache);
     $MRO{$class} = { MRO => \@MRO };
-    my $has_overload_fallback = 0;
+    my $has_overload_fallback;
     my %methods;
     # NOTE: 
     # we do @MRO[1 .. $#MRO] here because it
@@ -88,7 +134,7 @@ sub _calculate_method_dispatch_table {
         # have use "fallback", then we want to
         # grab that value 
         $has_overload_fallback = ${"${local}::()"} 
-            if defined ${"${local}::()"};
+            if !defined $has_overload_fallback && defined ${"${local}::()"};
         foreach my $method (grep { defined &{"${local}::$_"} } keys %{"${local}::"}) {
             # skip if already overriden in local class
             next unless !defined *{"${class}::$method"}{CODE};
@@ -104,28 +150,37 @@ sub _calculate_method_dispatch_table {
 }
 
 sub _apply_method_dispatch_tables {
+    return if $C3_IN_CORE;
     foreach my $class (keys %MRO) {
         _apply_method_dispatch_table($class);
     }     
 }
 
 sub _apply_method_dispatch_table {
+    return if $C3_IN_CORE;
     my $class = shift;
     no strict 'refs';
     ${"${class}::()"} = $MRO{$class}->{has_overload_fallback}
-        if $MRO{$class}->{has_overload_fallback};
+        if !defined &{"${class}::()"}
+           && defined $MRO{$class}->{has_overload_fallback};
     foreach my $method (keys %{$MRO{$class}->{methods}}) {
+        if ( $method =~ /^\(/ ) {
+            my $orig = $MRO{$class}->{methods}->{$method}->{orig};
+            ${"${class}::$method"} = $$orig if defined $$orig;
+        }
         *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code};
     }    
 }
 
 sub _remove_method_dispatch_tables {
+    return if $C3_IN_CORE;
     foreach my $class (keys %MRO) {
         _remove_method_dispatch_table($class);
-    }       
+    }
 }
 
 sub _remove_method_dispatch_table {
+    return if $C3_IN_CORE;
     my $class = shift;
     no strict 'refs';
     delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback};    
@@ -133,85 +188,32 @@ sub _remove_method_dispatch_table {
         delete ${"${class}::"}{$method}
             if defined *{"${class}::${method}"}{CODE} && 
                (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code});       
-    }   
+    }
 }
 
-## functions for calculating C3 MRO
-
 sub calculateMRO {
-    my ($class) = @_;
+    my ($class, $merge_cache) = @_;
+
     return Algorithm::C3::merge($class, sub { 
         no strict 'refs'; 
         @{$_[0] . '::ISA'};
-    });
+    }, $merge_cache);
 }
 
-package  # hide me from PAUSE
-    next; 
-
-use strict;
-use warnings;
-
-use Scalar::Util 'blessed';
-
-our $VERSION = '0.05';
+# Method overrides to support 5.9.5+ or Class::C3::XS
 
-our %METHOD_CACHE;
+sub _core_calculateMRO { @{mro::get_linear_isa($_[0], 'c3')} }
 
-sub method {
-    my $indirect = caller() =~ /^(?:next|maybe::next)$/;
-    my $level = $indirect ? 2 : 1;
-     
-    my ($method_caller, $label, @label);
-    while ($method_caller = (caller($level++))[3]) {
-      @label = (split '::', $method_caller);
-      $label = pop @label;
-      last unless
-        $label eq '(eval)' ||
-        $label eq '__ANON__';
-    }
-    my $caller   = join '::' => @label;    
-    my $self     = $_[0];
-    my $class    = blessed($self) || $self;
-    
-    my $method = $METHOD_CACHE{"$class|$caller|$label"} ||= do {
-        
-        my @MRO = Class::C3::calculateMRO($class);
-        
-        my $current;
-        while ($current = shift @MRO) {
-            last if $caller eq $current;
-        }
-        
-        no strict 'refs';
-        my $found;
-        foreach my $class (@MRO) {
-            next if (defined $Class::C3::MRO{$class} && 
-                     defined $Class::C3::MRO{$class}{methods}{$label});          
-            last if (defined ($found = *{$class . '::' . $label}{CODE}));
-        }
-        
-        $found;
-    };
-
-    return $method if $indirect;
-
-    die "No next::method '$label' found for $self" if !$method;
-
-    goto &{$method};
+if($C3_IN_CORE) {
+    no warnings 'redefine';
+    *Class::C3::calculateMRO = \&_core_calculateMRO;
+}
+elsif($C3_XS) {
+    no warnings 'redefine';
+    *Class::C3::calculateMRO = \&Class::C3::XS::calculateMRO;
+    *Class::C3::_calculate_method_dispatch_table
+        = \&Class::C3::XS::_calculate_method_dispatch_table;
 }
-
-sub can { method($_[0]) }
-
-package  # hide me from PAUSE
-    maybe::next; 
-
-use strict;
-use warnings;
-
-our $VERSION = '0.01';
-
-sub method { (next::method($_[0]) || return)->(@_) }
 
 1;
 
@@ -357,7 +359,9 @@ any other users other than the L<DBIx::Class> folks). The simplest solution of c
 your own INIT method which calls this function. 
 
 NOTE: 
-This can B<not> be used to re-load the dispatch tables for all classes. Use C<reinitialize> for that.
+
+If C<initialize> detects that C<initialize> has already been executed, it will L</uninitialize> and
+clear the MRO cache first.
 
 =item B<uninitialize>
 
@@ -366,11 +370,7 @@ style dispatch order (depth-first, left-to-right).
 
 =item B<reinitialize>
 
-This effectively calls C<uninitialize> followed by C<initialize> the result of which is a reloading of
-B<all> the calculated C3 dispatch tables. 
-
-It should be noted that if you have a large class library, this could potentially be a rather costly 
-operation.
+This is an alias for L</initialize> above.
 
 =back
 
@@ -483,18 +483,19 @@ limitation of this module.
 
 =back
 
-=head1 CODE COVERAGE
+=head1 COMPATIBILITY
 
-I use B<Devel::Cover> to test the code coverage of my tests, below is the B<Devel::Cover> report on this 
-module's test suite.
+If your software requires Perl 5.9.5 or higher, you do not need L<Class::C3>, you can simply C<use mro 'c3'>, and not worry about C<initialize()>, avoid some of the above caveats, and get the best possible performance.  See L<mro> for more details.
 
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
- File                           stmt   bran   cond    sub    pod   time  total
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
- Class/C3.pm                    98.3   84.4   80.0   96.2  100.0   98.4   94.4
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
- Total                          98.3   84.4   80.0   96.2  100.0   98.4   94.4
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
+If your software is meant to work on earlier Perls, use L<Class::C3> as documented here.  L<Class::C3> will detect Perl 5.9.5+ and take advantage of the core support when available.
+
+=head1 Class::C3::XS
+
+This module will load L<Class::C3::XS> if it's installed and you are running on a Perl version older than 5.9.5.  Installing this is recommended when possible, as it results in significant performance improvements (but unlike the 5.9.5+ core support, it still has all of the same caveats as L<Class::C3>).
+
+=head1 CODE COVERAGE
+
+L<Devel::Cover> was reporting 94.4% overall test coverage earlier in this module's life.  Currently, the test suite does things that break under coverage testing, but it is fair to assume the coverage is still close to that value.
 
 =head1 SEE ALSO
 
@@ -552,12 +553,17 @@ and finding many bugs and providing fixes.
 =item Thanks to Justin Guenther for making C<next::method> more robust by handling 
 calls inside C<eval> and anon-subs.
 
+=item Thanks to Robert Norris for adding support for C<next::can> and 
+C<maybe::next::method>.
+
 =back
 
 =head1 AUTHOR
 
 Stevan Little, E<lt>stevan@iinteractive.comE<gt>
 
+Brandon L. Black, E<lt>blblack@gmail.comE<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
 Copyright 2005, 2006 by Infinity Interactive, Inc.
@@ -567,4 +573,4 @@ L<http://www.iinteractive.com>
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself. 
 
-=cut
\ No newline at end of file
+=cut