package Class::Multi; # # Copyright (C) 2005 by K Cody # All rights reserved. # # This software may be modified and/or redistributed under the # terms of the GNU Lesser General Public License v2.1 or later. # =head1 NAME Class::Multi - Multiple inheritance support functions. =head1 SYNOPSIS =over =item * A flexible inheritance traversal function. =item * A calling-class-relative metaphor of C. =item * A calling-class-relative metaphor of C. =back =cut use strict; use warnings; use Exporter; # old syntax for PERL 5.004 compat use vars qw( $VERSION @ISA @EXPORT_OK ); $VERSION = '1.00'; @ISA = qw( Exporter ); @EXPORT_OK = qw( &walk &other &otherpkg ); =head1 Inheritance Traversal Function =head2 C<< walk( \&testsub, CLASS, @avoid ) >> Executes the supplied code reference once for each superclass of the supplied derived class, in the same depth-first order that PERL uses internally. If an @avoid list is supplied, the code reference will not be executed until all classes in that list have been seen. =head2 C<< walk { BLOCK } $derived >> Executes the { BLOCK } once each for $derived and its superclasses. =head2 C<< walk { BLOCK } $derived, $derived >> Executes the { BLOCK } only for $derived's superclasses. =head2 C<< walk { BLOCK } $derived, __PACKAGE__ >> Executes the { BLOCK } only for classes that are inherited after the class in which the expression is found. =cut sub walk(&$;@) { my $callout = shift; my $derived = shift; my %looking = map { $_ => 1 } @_; # prototyping will catch PEBKACs involving $callout return unless defined $derived && length( $derived ); # the class search is governed by an inverted stack (unshift/shift) # inverted to avoid having to reverse( @{"$class\::ISA"} ); my @stack = ( $derived ); my ( %trail, $class, $rc ); while ( $class = shift @stack ) { next unless defined( $class ) && length( $class ); # canonize main:: fudgery substr( $class, 0, 2 ) = 'main::' if substr( $class, 0, 2 ) eq '::'; # push $class's supers to stack { no strict 'refs'; # access symbol table unshift @stack, @{"$class\::ISA"}; } # found a class in the avoidance list if ( exists $looking{$class} ) { delete $looking{$class}; $trail{$class}++; next; } # the avoidance list isn't empty, do not execute if ( keys %looking ) { $trail{$class}++; next; } # visit each class only once - "diamond" inheritance unless ( exists $trail{$class} && $trail{$class} ) { $rc = &$callout( $class ); } # if something nonzero was returned, the loop is done return $rc if defined $rc && $rc; $trail{$class}++; } return undef; } =head1 Multi-Inherited Method Search =head2 C<< other( $this, METHOD ) >> C checks if the object or class $this has a method called 'METHOD', that occurs -AFTER- the calling class in the inheritance tree. Usage and semantics are otherwise identical to C The calling class is inferred via C. =cut sub other($$) { my ( $this, $name ) = @_; my ( $origin, $caller ); # a valid class or instance must be supplied $origin = ref( $this ) || $this or return; # we must be called from code that has a package reference $caller = caller() or return; # symbol table lookup would be undef if the method doesn't exist return walk { no strict 'refs'; \&{"$_\::$name"} } $origin, $caller; } =head2 C<< otherpkg( $this, METHOD ) >> Identical to C, except the package name is returned instead of the desired method's code reference. =cut sub otherpkg($$) { my ( $this, $name ) = @_; my ( $origin, $caller ); # a valid class or instance must be supplied $origin = ref( $this ) || $this or return; # we must be called from code that has a package reference $caller = caller() or return; # symbol table lookup would be undef if the method doesn't exist return walk { no strict 'refs'; \&{"$_\::$name"} ? $_ : undef; } $origin, $caller; } =head1 Multi-Inherited Mandatory Method Call =head2 C<< $this->OTHER::mymethod( @myargs ); >> Syntactic sugar. Equivalent to C<< &{other( $this, 'mymethod' )( @myargs )}; >>. Like C, C expects the requested method to exist. If it does not, an exception is thrown. =cut package OTHER; use strict; use warnings; # old syntax for PERL 5.004 compat use vars qw( $AUTOLOAD ); use Carp; sub AUTOLOAD { my $this = shift; my ( $origin, $caller, $name, $func ); # a valid class or instance must be supplied $origin = ref( $this ) || $this or return; # we must be called from code that has a package reference $caller = caller() or return; # strip any package name from the supplied method name ( $name = $AUTOLOAD ) =~ s/.*://; # can't just call other() above, would change caller package ;) $func = walk { no strict 'refs'; \&{"$_\::$name"} } $origin, $caller; # using this syntax indicates a method is -expected- to exist unless ( defined( $func ) && ref( $func ) eq 'CODE' ) { confess( "No method '$name' after '$caller' in '$origin'.\n" ); } return &$func( $this, @_ ); } 1;