use Carp 'confess';
use Scalar::Util 'blessed', 'reftype';
use Sub::Name 'subname';
-use SUPER ();
+use B 'svref_2object';
our $VERSION = '0.06';
shift @class_list; # shift off $self->name
foreach my $class_name (@class_list) {
- my $meta = $METAS{$class_name};
+ my $meta = $METAS{$class_name} || next;
($self->isa(blessed($meta)))
|| confess $self->name . "->meta => (" . (blessed($self)) . ")" .
" is not compatible with the " .
sub version {
my $self = shift;
- no strict 'refs';
- ${$self->name . '::VERSION'};
+ ${$self->get_package_variable('$VERSION')};
}
# Inheritance
sub superclasses {
my $self = shift;
- no strict 'refs';
if (@_) {
my @supers = @_;
- @{$self->name . '::ISA'} = @supers;
+ @{$self->get_package_variable('@ISA')} = @supers;
}
- @{$self->name . '::ISA'};
+ @{$self->get_package_variable('@ISA')};
}
sub class_precedence_list {
my $method = $self->get_method($method_name);
# if we dont have local ...
unless ($method) {
- # create a local which just calls the SUPER method ...
- $self->add_method($method_name => sub { $_[0]->super($method_name)->(@_) });
+ # make sure this method even exists ...
+ ($self->find_next_method_by_name($method_name))
+ || confess "The method '$method_name' is not found in the inherience hierarchy for this class";
+ # if so, then create a local which just
+ # calls the next applicable method ...
+ $self->add_method($method_name => sub {
+ $self->find_next_method_by_name($method_name)->(@_);
+ });
$method = $self->get_method($method_name);
}
no strict 'refs';
return 0 if !defined(&{$sub_name});
-
my $method = \&{$sub_name};
- $method = $self->method_metaclass->wrap($method) unless blessed($method);
+ return 0 if (svref_2object($method)->GV->STASH->NAME || '') ne $self->name &&
+ (svref_2object($method)->GV->NAME || '') ne '__ANON__';
- return 0 if $method->package_name ne $self->name &&
- $method->name ne '__ANON__';
+ # at this point we are relatively sure
+ # it is our method, so we bless/wrap it
+ $self->method_metaclass->wrap($method) unless blessed($method);
return 1;
}
next if $seen_class{$class};
$seen_class{$class}++;
# fetch the meta-class ...
- my $meta = $self->initialize($class);;
+ my $meta = $self->initialize($class);
push @methods => {
name => $method_name,
class => $class,
return @methods;
}
+sub find_next_method_by_name {
+ my ($self, $method_name) = @_;
+ (defined $method_name && $method_name)
+ || confess "You must define a method name to find";
+ # keep a record of what we have seen
+ # here, this will handle all the
+ # inheritence issues because we are
+ # using the &class_precedence_list
+ my %seen_class;
+ my @cpl = $self->class_precedence_list();
+ shift @cpl; # discard ourselves
+ foreach my $class (@cpl) {
+ next if $seen_class{$class};
+ $seen_class{$class}++;
+ # fetch the meta-class ...
+ my $meta = $self->initialize($class);
+ return $meta->get_method($method_name)
+ if $meta->has_method($method_name);
+ }
+ return;
+}
+
## Attributes
sub add_attribute {
initialization and destruction where you only want the method called
once, and in the correct order.
+=item B<find_next_method_by_name ($method_name)>
+
+This will return the first method to match a given C<$method_name> in
+the superclasses, this is basically equivalent to calling
+C<SUPER::$method_name>, but it can be dispatched at runtime.
+
=back
=head2 Method Modifiers
+Method modifiers are a concept borrowed from CLOS, in which a method
+can be wrapped with I<before>, I<after> and I<around> method modifiers
+that will be called everytime the method is called.
+
+=head3 How method modifiers work?
+
+Method modifiers work by wrapping the original method and then replacing
+it in the classes symbol table. The wrappers will handle calling all the
+modifiers in the appropariate orders and preserving the calling context
+for the original method.
+
+Each method modifier serves a particular purpose, which may not be
+obvious to users of other method wrapping modules. To start with, the
+return values of I<before> and I<after> modifiers are ignored. This is
+because thier purpose is B<not> to filter the input and output of the
+primary method (this is done with an I<around> modifier). This may seem
+like an odd restriction to some, but doing this allows for simple code
+to be added at the begining or end of a method call without jeapordizing
+the normal functioning of the primary method or placing any extra
+responsibility on the code of the modifier. Of course if you have more
+complex needs, then use the I<around> modifier, which uses a variation
+of continutation passing style to allow for a high degree of flexibility.
+
+Before and around modifiers are called in last-defined-first-called order,
+while after modifiers are called in first-defined-first-called order. So
+the call tree might looks something like this:
+
+ before 2
+ before 1
+ around 2
+ around 1
+ primary
+ after 1
+ after 2
+
+To see examples of using method modifiers, see the following examples
+included in the distribution; F<InstanceCountingClass>, F<Perl6Attribute>,
+F<AttributesWithHistory> and F<C3MethodDispatchOrder>. There is also a
+classic CLOS usage example in the test F<017_add_method_modifier.t>.
+
+=head3 What is the performance impact?
+
+Of course there is a performance cost associated with method modifiers,
+but we have made every effort to make that cost be directly proportional
+to the amount of modifier features you utilize.
+
+The wrapping method does it's best to B<only> do as much work as it
+absolutely needs to. In order to do this we have moved some of the
+performance costs to set-up time, where they are easier to amortize.
+
+All this said, my benchmarks have indicated the following:
+
+ simple wrapper with no modifiers 100% slower
+ simple wrapper with simple before modifier 400% slower
+ simple wrapper with simple after modifier 450% slower
+ simple wrapper with simple around modifier 500-550% slower
+ simple wrapper with all 3 modifiers 1100% slower
+
+These numbers may seem daunting, but you must remember, every feature
+comes with some cost. To put things in perspective, just doing a simple
+C<AUTOLOAD> which does nothing but extract the name of the method called
+and return it costs about 400% over a normal method call.
+
=over 4
=item B<add_before_method_modifier ($method_name, $code)>
+This will wrap the method at C<$method_name> and the supplied C<$code>
+will be passed the C<@_> arguments, and called before the original
+method is called. As specified above, the return value of the I<before>
+method modifiers is ignored, and it's ability to modify C<@_> is
+fairly limited. If you need to do either of these things, use an
+C<around> method modifier.
+
=item B<add_after_method_modifier ($method_name, $code)>
+This will wrap the method at C<$method_name> so that the original
+method will be called, it's return values stashed, and then the
+supplied C<$code> will be passed the C<@_> arguments, and called.
+As specified above, the return value of the I<after> method
+modifiers is ignored, and it cannot modify the return values of
+the original method. If you need to do either of these things, use an
+C<around> method modifier.
+
=item B<add_around_method_modifier ($method_name, $code)>
+This will wrap the method at C<$method_name> so that C<$code>
+will be called and passed the original method as an extra argument
+at the begining of the C<@_> argument list. This is a variation of
+continuation passing style, where the function prepended to C<@_>
+can be considered a continuation. It is up to C<$code> if it calls
+the original method or not, there is no restriction on what the
+C<$code> can or cannot do.
+
=back
=head2 Attributes