got rid of PurePerl in classnames, fixed up a few other things, possible alpha releas...
[gitmo/Class-C3.git] / lib / Class / C3.pm
index a2f4740..8e7dc0a 100644 (file)
@@ -4,10 +4,34 @@ package Class::C3;
 use strict;
 use warnings;
 
-use Scalar::Util 'blessed';
-use Algorithm::C3;
-
-our $VERSION = '0.12';
+our $VERSION = '0.15_01';
+
+# Class::C3 defines Class::C3::* in pure perl
+# if mro, it does nothing else
+#   elsif Class::C3::XS, do nothing else
+#     else load next.pm
+# Class::C3::XS defines the same routines as next.pm,
+#  and also redefines (suppress warning) calculateMRO
+#  (ditto for anything else in Class::C3::* we want to
+#   XS-ize).
+
+our $C3_IN_CORE;
+
+BEGIN {
+    eval "require mro"; # XXX in the future, this should be a version check
+    if($@) {
+        die $@ if $@ !~ /locate/;
+        eval "require Class::C3::XS";
+        if($@) {
+            die $@ if $@ !~ /locate/;
+            eval "require Algorithm::C3; require Class::C3::next";
+            die $@ if $@;
+        }
+    }
+    else {
+        $C3_IN_CORE = 1;
+    }
+}
 
 # this is our global stash of both 
 # MRO's and method dispatch tables
@@ -29,12 +53,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};
@@ -43,39 +73,53 @@ sub import {
 ## initializers
 
 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 }
 
 ## 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 %methods;
@@ -104,12 +148,14 @@ 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}
@@ -120,12 +166,14 @@ sub _apply_method_dispatch_table {
 }
 
 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};    
@@ -136,69 +184,17 @@ sub _remove_method_dispatch_table {
     }   
 }
 
-## functions for calculating C3 MRO
-
 sub calculateMRO {
-    my ($class) = @_;
+    my ($class, $merge_cache) = @_;
+
+    return @{mro::get_linear_isa($class)} if $C3_IN_CORE;
+
     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';
-
-our %METHOD_CACHE;
-
-sub _find {
-    my $level = 2;
-    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;
-    
-    return $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}));
-        }
-        
-        die "No next::method '$label' found for $_[0]" if $_[1] && !$found;
-        
-        $found;
-    };
-}
-
-sub method { goto &{_find($_[0], 1)} }
-
-sub can { return _find($_[0], 0) ? 1 : 0 }
-
 1;
 
 __END__
@@ -343,7 +339,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>
 
@@ -352,11 +350,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
 
@@ -406,6 +400,10 @@ You can use C<next::can> to see if C<next::method> will succeed before you call
 
   $self->next::method(@_) if $self->next::can; 
 
+Additionally, you can use C<maybe::next::method> as a shortcut to only call the next method if it exists. 
+The previous example could be simply written as:
+
+  $self->maybe::next::method(@_);
 
 There are some caveats about using C<next::method>, see below for those.
 
@@ -534,12 +532,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.
@@ -549,4 +552,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