break out most of the code to ::PurePerl
[gitmo/Class-C3.git] / lib / Class / C3.pm
index 2d0d04f..5c62422 100644 (file)
@@ -4,195 +4,16 @@ package Class::C3;
 use strict;
 use warnings;
 
-use Scalar::Util 'blessed';
-use Algorithm::C3;
-
-our $VERSION = '0.11';
-
-# this is our global stash of both 
-# MRO's and method dispatch tables
-# the structure basically looks like
-# this:
-#
-#   $MRO{$class} = {
-#      MRO => [ <class precendence list> ],
-#      methods => {
-#          orig => <original location of method>,
-#          code => \&<ref to original method>
-#      },
-#      has_overload_fallback => (1 | 0)
-#   }
-#
-our %MRO;
-
-# use these for debugging ...
-sub _dump_MRO_table { %MRO }
-our $TURN_OFF_C3 = 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;
-    # make a note to calculate $class 
-    # during INIT phase
-    $MRO{$class} = undef unless exists $MRO{$class};
-}
-
-## initializers
-
-sub initialize {
-    # why bother if we don't have anything ...
-    return unless keys %MRO;
-    _calculate_method_dispatch_tables();
-    _apply_method_dispatch_tables();
-    %next::METHOD_CACHE = ();
-}
-
-sub uninitialize {
-    # why bother if we don't have anything ...
-    return unless keys %MRO;    
-    _remove_method_dispatch_tables();    
-    %next::METHOD_CACHE = ();
-}
-
-sub reinitialize {
-    uninitialize();
-    # clean up the %MRO before we re-initialize
-    $MRO{$_} = undef foreach keys %MRO;
-    initialize();
-}
-
-## functions for applying C3 to classes
-
-sub _calculate_method_dispatch_tables {
-    foreach my $class (keys %MRO) {
-        _calculate_method_dispatch_table($class);
-    }
-}
-
-sub _calculate_method_dispatch_table {
-    my $class = shift;
-    no strict 'refs';
-    my @MRO = calculateMRO($class);
-    $MRO{$class} = { MRO => \@MRO };
-    my $has_overload_fallback = 0;
-    my %methods;
-    # NOTE: 
-    # we do @MRO[1 .. $#MRO] here because it
-    # makes no sense to interogate the class
-    # which you are calculating for. 
-    foreach my $local (@MRO[1 .. $#MRO]) {
-        # if overload has tagged this module to 
-        # have use "fallback", then we want to
-        # grab that value 
-        $has_overload_fallback = ${"${local}::()"} 
-            if defined ${"${local}::()"};
-        foreach my $method (grep { defined &{"${local}::$_"} } keys %{"${local}::"}) {
-            # skip if already overriden in local class
-            next unless !defined *{"${class}::$method"}{CODE};
-            $methods{$method} = {
-                orig => "${local}::$method",
-                code => \&{"${local}::$method"}
-            } unless exists $methods{$method};
+our $VERSION = '0.15';
+
+BEGIN {
+    eval { require Class::C3::XS };
+    if($@) {
+        eval { require Class::C3::PurePerl };
+        if($@) {
+            die 'Could not load Class::C3::XS or Class::C3::PurePerl!';
         }
-    }    
-    # now stash them in our %MRO table
-    $MRO{$class}->{methods} = \%methods; 
-    $MRO{$class}->{has_overload_fallback} = $has_overload_fallback;        
-}
-
-sub _apply_method_dispatch_tables {
-    foreach my $class (keys %MRO) {
-        _apply_method_dispatch_table($class);
-    }     
-}
-
-sub _apply_method_dispatch_table {
-    my $class = shift;
-    no strict 'refs';
-    ${"${class}::()"} = $MRO{$class}->{has_overload_fallback}
-        if $MRO{$class}->{has_overload_fallback};
-    foreach my $method (keys %{$MRO{$class}->{methods}}) {
-        *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code};
-    }    
-}
-
-sub _remove_method_dispatch_tables {
-    foreach my $class (keys %MRO) {
-        _remove_method_dispatch_table($class);
-    }       
-}
-
-sub _remove_method_dispatch_table {
-    my $class = shift;
-    no strict 'refs';
-    delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback};    
-    foreach my $method (keys %{$MRO{$class}->{methods}}) {
-        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) = @_;
-    return Algorithm::C3::merge($class, sub { 
-        no strict 'refs'; 
-        @{$_[0] . '::ISA'};
-    });
-}
-
-package  # hide me from PAUSE
-    next; 
-
-use strict;
-use warnings;
-
-use Scalar::Util 'blessed';
-
-our $VERSION = '0.05';
-
-our %METHOD_CACHE;
-
-sub method {
-    my $level = 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;
-    
-    goto &{ $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 $self" unless $found;
-
-      $found;
-    } };
 }
 
 1;
@@ -315,7 +136,21 @@ Given a C<$class> this will return an array of class names in the proper C3 meth
 =item B<initialize>
 
 This B<must be called> to initalize the C3 method dispatch tables, this module B<will not work> if 
-you do not do this. It is advised to do this as soon as possible B<after> any classes which use C3.
+you do not do this. It is advised to do this as soon as possible B<after> loading any classes which 
+use C3. Here is a quick code example:
+  
+  package Foo;
+  use Class::C3;
+  # ... Foo methods here
+  
+  package Bar;
+  use Class::C3;
+  use base 'Foo';
+  # ... Bar methods here
+  
+  package main;
+  
+  Class::C3::initialize(); # now it is safe to use Foo and Bar
 
 This function used to be called automatically for you in the INIT phase of the perl compiler, but 
 that lead to warnings if this module was required at runtime. After discussion with my user base 
@@ -325,7 +160,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>
 
@@ -334,11 +171,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
 
@@ -383,6 +216,16 @@ that you cannot dispatch to a method of a different name (this is how C<NEXT::>
 The next thing to keep in mind is that you will need to pass all arguments to C<next::method> it can 
 not automatically use the current C<@_>. 
 
+If C<next::method> cannot find a next method to re-dispatch the call to, it will throw an exception.
+You can use C<next::can> to see if C<next::method> will succeed before you call it like so:
+
+  $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.
 
 =head1 CAVEATS
@@ -510,12 +353,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.
@@ -525,4 +373,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