Improve get_method/has_method/add_method not to use get_method_map.
gfx [Fri, 10 Jul 2009 03:26:44 +0000 (12:26 +0900)]
Normal method objects, or implicit methods, are no longer created until required.
bench/loading-benchmark.pl says this makes loading time 10% faster.

lib/Class/MOP/Class.pm
lib/Class/MOP/Method.pm
t/010_self_introspection.t

index 8810338..90df86a 100644 (file)
@@ -347,6 +347,8 @@ sub constructor_class        { $_[0]->{'constructor_class'}           }
 sub constructor_name         { $_[0]->{'constructor_name'}            }
 sub destructor_class         { $_[0]->{'destructor_class'}            }
 
+sub _method_map              { $_[0]->{'methods'}                     }
+
 # Instance Construction & Cloning
 
 sub new_object {
@@ -616,15 +618,16 @@ sub add_method {
                 name         => $method_name            
             ) if $method->can('clone');
         }
+
+        $method->attach_to_class($self);
+        $self->_method_map->{$method_name} = $method;
     }
     else {
+        # If a raw code reference is supplied, its method object is not created.
+        # The method object won't be created until required.
         $body = $method;
-        $method = $self->wrap_method_body( body => $body, name => $method_name );
     }
 
-    $method->attach_to_class($self);
-
-    $self->get_method_map->{$method_name} = $method;
 
     my ( $current_package, $current_name ) = Class::MOP::get_code_info($body);
 
@@ -721,7 +724,7 @@ sub has_method {
     (defined $method_name && $method_name)
         || confess "You must define a method name";
 
-    exists $self->get_method_map->{$method_name};
+    defined $self->get_method($method_name);
 }
 
 sub get_method {
@@ -729,7 +732,85 @@ sub get_method {
     (defined $method_name && $method_name)
         || confess "You must define a method name";
 
-    return $self->get_method_map->{$method_name};
+    my $class_name = $self->name;
+    my $method_map = $self->_method_map;
+
+    my $method_object = $method_map->{$method_name};
+
+    if(!$method_object){
+        my $glob = $self->namespace->{$method_name};
+
+        if(!defined $glob){
+            return undef;
+        }
+
+        my $code;
+        if(ref(\$glob) eq 'GLOB'){
+            $code = *{$glob}{CODE};
+            if(!defined $code){
+                return undef;
+            }
+            my($code_package, $code_name) = Class::MOP::get_code_info($code);
+
+            if(!$code_package
+                    || ( !($code_package eq 'constant' && $code_name eq '__ANON__')
+                            && $code_package ne $class_name ) ){
+                return undef;
+            }
+        }
+        else{ # stubs or constants
+            no strict 'refs';
+            $code = \&{$class_name . '::' . $method_name};
+        }
+        $method_object = $method_map->{$method_name} = $self->wrap_method_body(
+            body                 => $code,
+            name                 => $method_name,
+            associated_metaclass => $self,
+       );
+       $method_object->_update_generation();
+    }
+    else{ # $method_object already exists
+        if(!$method_object->_is_valid_generation){
+            my $glob = $self->namespace->{$method_name};
+            if(!defined $glob){
+                delete $method_map->{$method_name};
+                return undef;
+            }
+
+            my $code;
+            if(ref(\$glob) eq 'GLOB'){
+                $code = *{$glob}{CODE};
+                if(!defined($code)){
+                    delete $method_map->{$method_name};
+                    return undef;
+                }
+            }
+            else{ # stubs or constants
+                no strict 'refs';
+                $code = \&{$class_name . '::' . $method_name};
+            }
+
+            if($method_object->body != $code){ # changed for some reason
+                 my($code_package, $code_name) = Class::MOP::get_code_info($code);
+                 if(!$code_package
+                         || ( !($code_package eq 'constant' && $code_name eq '__ANON__')
+                                 && $code_package ne $class_name ) ){
+                     delete $method_map->{$method_name};
+                     return undef;
+                 }
+
+                 # update $method_map
+                 $method_object = $method_map->{$method_name} = $self->wrap_method_body(
+                     body                 => $code,
+                     name                 => $method_name,
+                     associated_metaclass => $self,
+                );
+            }
+            $method_object->_update_generation();
+        }
+    }
+
+    return $method_object;
 }
 
 sub remove_method {
index ea580ab..c6e7afc 100644 (file)
@@ -57,6 +57,25 @@ sub _new {
 
 sub associated_metaclass { shift->{'associated_metaclass'} }
 
+sub _is_valid_generation{
+    my($self) = @_;
+    my $metaclass = $self->associated_metaclass;
+
+    if($metaclass){
+        return( ($self->{_generation} || 0) == Class::MOP::check_package_cache_flag($metaclass->name) );
+    }
+    else{
+        return 1;
+    }
+}
+
+sub _update_generation {
+    my($self) = @_;
+    my $metaclass = $self->associated_metaclass
+        or confess("No metaclass associated to the method " . $self->name);
+    $self->{_generation} = Class::MOP::check_package_cache_flag($metaclass->name);
+}
+
 sub attach_to_class {
     my ( $self, $class ) = @_;
     $self->{associated_metaclass} = $class;
index 5c870ce..dcac510 100644 (file)
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 296;
+use Test::More tests => 298;
 use Test::Exception;
 
 use Class::MOP;
@@ -75,6 +75,7 @@ my @class_mop_class_methods = qw(
     superclasses subclasses direct_subclasses class_precedence_list
     linearized_isa _superclasses_updated
 
+    _method_map
     has_method get_method add_method remove_method alias_method wrap_method_body
     get_method_list get_method_map get_all_method_names get_all_methods compute_all_applicable_methods
         find_method_by_name find_all_methods_by_name find_next_method_by_name