immutable refacotring
[gitmo/Class-MOP.git] / lib / Class / MOP / Class.pm
index 88842f3..96d1402 100644 (file)
@@ -4,18 +4,19 @@ package Class::MOP::Class;
 use strict;
 use warnings;
 
+use Class::MOP::Instance;
+use Class::MOP::Method::Wrapped;
+
 use Carp         'confess';
 use Scalar::Util 'blessed', 'reftype', 'weaken';
 use Sub::Name    'subname';
 use B            'svref_2object';
 
-our $VERSION   = '0.19';
+our $VERSION   = '0.21';
 our $AUTHORITY = 'cpan:STEVAN';
 
 use base 'Class::MOP::Module';
 
-use Class::MOP::Instance;
-
 # Self-introspection 
 
 sub meta { Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) }
@@ -69,7 +70,6 @@ sub construct_class_instance {
                         : blessed($class))
                     : $class);
 
-    $class = blessed($class) || $class;
     # now create the metaclass
     my $meta;
     if ($class =~ /^Class::MOP::Class$/) {
@@ -91,8 +91,8 @@ sub construct_class_instance {
             '$:version'             => \undef,
             '$:authority'           => \undef,
             # defined in Class::MOP::Class
-            '%:methods'             => \undef,
             
+            '%:methods'             => {},
             '%:attributes'          => {},            
             '$:attribute_metaclass' => $options{':attribute_metaclass'} || 'Class::MOP::Attribute',
             '$:method_metaclass'    => $options{':method_metaclass'}    || 'Class::MOP::Method',
@@ -173,13 +173,14 @@ sub check_metaclass_compatability {
 
     sub is_anon_class {
         my $self = shift;
+        no warnings 'uninitialized';
         $self->name =~ /^$ANON_CLASS_PREFIX/ ? 1 : 0;        
     }
 
     sub create_anon_class {
         my ($class, %options) = @_;   
         my $package_name = $ANON_CLASS_PREFIX . ++$ANON_CLASS_SERIAL;
-        return $class->create($package_name, '0.00', %options);
+        return $class->create($package_name, %options);
     } 
 
     # NOTE:
@@ -190,6 +191,7 @@ sub check_metaclass_compatability {
     # really need to be handled explicitly
     sub DESTROY {
         my $self = shift;
+        no warnings 'uninitialized';        
         return unless $self->name =~ /^$ANON_CLASS_PREFIX/;
         my ($serial_id) = ($self->name =~ /^$ANON_CLASS_PREFIX(\d+)/);
         no strict 'refs';     
@@ -204,14 +206,27 @@ sub check_metaclass_compatability {
 # creating classes with MOP ...
 
 sub create {
-    my ($class, $package_name, $package_version, %options) = @_;
+    my $class        = shift;
+    my $package_name = shift;
+    
     (defined $package_name && $package_name)
         || confess "You must pass a package name";
+
+    (scalar @_ % 2 == 0)
+        || confess "You much pass all parameters as name => value pairs " . 
+                   "(I found an uneven number of params in \@_)";
+
+    my (%options) = @_;
+    
     my $code = "package $package_name;";
-    $code .= "\$$package_name\:\:VERSION = '$package_version';" 
-        if defined $package_version;
+    $code .= "\$$package_name\:\:VERSION = '" . $options{version} . "';" 
+        if exists $options{version};
+    $code .= "\$$package_name\:\:AUTHORITY = '" . $options{authority} . "';" 
+        if exists $options{authority};  
+              
     eval $code;
     confess "creation of $package_name failed : $@" if $@;    
+    
     my $meta = $class->initialize($package_name);
     
     $meta->add_method('meta' => sub { 
@@ -249,18 +264,30 @@ sub attribute_metaclass { $_[0]->{'$:attribute_metaclass'} }
 sub method_metaclass    { $_[0]->{'$:method_metaclass'}    }
 sub instance_metaclass  { $_[0]->{'$:instance_metaclass'}  }
 
-sub get_method_map {
+# FIXME:
+# this is a prime canidate for conversion to XS
+sub get_method_map {    
     my $self = shift;
-    # FIXME:
-    # there is a faster/better way 
-    # to do this, I am sure :)    
-    return +{ 
-        map {
-            $_ => $self->get_method($_) 
-        } grep { 
-            $self->has_method($_) 
-        } $self->list_all_package_symbols
-    };
+    my $map  = $self->{'%:methods'}; 
+    
+    my $class_name       = $self->name;
+    my $method_metaclass = $self->method_metaclass;
+    
+    foreach my $symbol ($self->list_all_package_symbols('CODE')) {
+        my $code = $self->get_package_symbol('&' . $symbol);
+        
+        next if exists  $map->{$symbol} && 
+                defined $map->{$symbol} && 
+                        $map->{$symbol}->body == $code;        
+        
+        my $gv = svref_2object($code)->GV;
+        next if ($gv->STASH->NAME || '') ne $class_name &&
+                ($gv->NAME        || '') ne '__ANON__';        
+        
+        $map->{$symbol} = $method_metaclass->wrap($code);
+    }
+    
+    return $map;
 }
 
 # Instance Construction & Cloning
@@ -362,16 +389,21 @@ sub add_method {
     my ($self, $method_name, $method) = @_;
     (defined $method_name && $method_name)
         || confess "You must define a method name";
-    # use reftype here to allow for blessed subs ...
-    ('CODE' eq (reftype($method) || ''))
-        || confess "Your code block must be a CODE reference";
-    my $full_method_name = ($self->name . '::' . $method_name);    
-
-    # FIXME:
-    # dont bless subs, its bad mkay
-    $method = $self->method_metaclass->wrap($method) unless blessed($method);
     
-    $self->add_package_symbol("&${method_name}" => subname $full_method_name => $method);
+    my $body;
+    if (blessed($method)) {
+        $body = $method->body;           
+    }
+    else {        
+        $body = $method;
+        ('CODE' eq (reftype($body) || ''))
+            || confess "Your code block must be a CODE reference";        
+        $method = $self->method_metaclass->wrap($body);        
+    }
+    $self->get_method_map->{$method_name} = $method;
+    
+    my $full_method_name = ($self->name . '::' . $method_name);        
+    $self->add_package_symbol("&${method_name}" => subname $full_method_name => $body);
 }
 
 {
@@ -385,7 +417,7 @@ sub add_method {
             $method = $self->find_next_method_by_name($method_name);
             # die if it does not exist
             (defined $method)
-                || confess "The method '$method_name' is not found in the inherience hierarchy for this class";
+                || confess "The method '$method_name' is not found in the inherience hierarchy for class " . $self->name;
             # and now make sure to wrap it 
             # even if it is already wrapped
             # because we need a new sub ref
@@ -442,20 +474,12 @@ sub alias_method {
     my ($self, $method_name, $method) = @_;
     (defined $method_name && $method_name)
         || confess "You must define a method name";
-    # use reftype here to allow for blessed subs ...
-    ('CODE' eq (reftype($method) || ''))
-        || confess "Your code block must be a CODE reference";
 
-    # FIXME:
-    # dont bless subs, its bad mkay
-    $method = $self->method_metaclass->wrap($method) unless blessed($method);    
+    my $body = (blessed($method) ? $method->body : $method);
+    ('CODE' eq (reftype($body) || ''))
+        || confess "Your code block must be a CODE reference";        
         
-    $self->add_package_symbol("&${method_name}" => $method);
-}
-
-sub find_method_by_name {
-    my ($self, $method_name) = @_;
-    return $self->name->can($method_name);
+    $self->add_package_symbol("&${method_name}" => $body);
 }
 
 sub has_method {
@@ -463,15 +487,7 @@ sub has_method {
     (defined $method_name && $method_name)
         || confess "You must define a method name";    
     
-    return 0 if !$self->has_package_symbol("&${method_name}");        
-    my $method = $self->get_package_symbol("&${method_name}");
-    return 0 if (svref_2object($method)->GV->STASH->NAME || '') ne $self->name &&
-                (svref_2object($method)->GV->NAME || '')        ne '__ANON__';      
-
-    # FIXME:
-    # dont bless subs, its bad mkay
-    $self->method_metaclass->wrap($method) unless blessed($method);
-    
+    return 0 unless exists $self->get_method_map->{$method_name};    
     return 1;
 }
 
@@ -479,10 +495,14 @@ sub get_method {
     my ($self, $method_name) = @_;
     (defined $method_name && $method_name)
         || confess "You must define a method name";
-
-    return unless $self->has_method($method_name);
+     
+    # NOTE:
+    # I don't really need this here, because
+    # if the method_map is missing a key it 
+    # will just return undef for me now
+    # return unless $self->has_method($method_name);
  
-    return $self->get_package_symbol("&${method_name}");
+    return $self->get_method_map->{$method_name};
 }
 
 sub remove_method {
@@ -492,15 +512,38 @@ sub remove_method {
     
     my $removed_method = $self->get_method($method_name);    
     
-    $self->remove_package_symbol("&${method_name}")
-        if defined $removed_method;
+    do { 
+        $self->remove_package_symbol("&${method_name}");
+        delete $self->get_method_map->{$method_name};
+    } if defined $removed_method;
         
     return $removed_method;
 }
 
 sub get_method_list {
     my $self = shift;
-    grep { $self->has_method($_) } $self->list_all_package_symbols;
+    keys %{$self->get_method_map};
+}
+
+sub find_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();
+    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;
 }
 
 sub compute_all_applicable_methods {
@@ -585,7 +628,20 @@ sub add_attribute {
     # make sure it is derived from the correct type though
     ($attribute->isa('Class::MOP::Attribute'))
         || confess "Your attribute must be an instance of Class::MOP::Attribute (or a subclass)";    
+
+    # first we attach our new attribute
+    # because it might need certain information 
+    # about the class which it is attached to
     $attribute->attach_to_class($self);
+    
+    # then we remove attributes of a conflicting 
+    # name here so that we can properly detach 
+    # the old attr object, and remove any 
+    # accessors it would have generated
+    $self->remove_attribute($attribute->name)
+        if $self->has_attribute($attribute->name);
+        
+    # then onto installing the new accessors
     $attribute->install_accessors();
     $self->get_attribute_map->{$attribute->name} = $attribute;
 }
@@ -602,8 +658,10 @@ sub get_attribute {
     (defined $attribute_name && $attribute_name)
         || confess "You must define an attribute name";
     return $self->get_attribute_map->{$attribute_name} 
-        if $self->has_attribute($attribute_name);   
-    return; 
+    # NOTE:
+    # this will return undef anyway, so no need ...
+    #    if $self->has_attribute($attribute_name);   
+    #return; 
 } 
 
 sub remove_attribute {
@@ -701,7 +759,8 @@ Class::MOP::Class - Class Meta Object
   
   # or use this to actually create classes ...
   
-  Class::MOP::Class->create('Bar' => '0.01' => (
+  Class::MOP::Class->create('Bar' => (
+      version      => '0.01',
       superclasses => [ 'Foo' ],
       attributes => [
           Class::MOP:::Attribute->new('$bar'),
@@ -752,15 +811,17 @@ created any more than nessecary. Basically, they are singletons.
 
 =over 4
 
-=item B<create ($package_name, ?$package_version,
+=item B<create ($package_name, 
+                version      =E<gt> ?$version,                 
+                authority    =E<gt> ?$authority,                                 
                 superclasses =E<gt> ?@superclasses, 
                 methods      =E<gt> ?%methods, 
                 attributes   =E<gt> ?%attributes)>
 
 This returns a B<Class::MOP::Class> object, bringing the specified 
-C<$package_name> into existence and adding any of the 
-C<$package_version>, C<@superclasses>, C<%methods> and C<%attributes> 
-to it.
+C<$package_name> into existence and adding any of the C<$version>, 
+C<$authority>, C<@superclasses>, C<%methods> and C<%attributes> to 
+it.
 
 =item B<create_anon_class (superclasses =E<gt> ?@superclasses, 
                            methods      =E<gt> ?%methods, 
@@ -967,8 +1028,11 @@ C<$method_name> is actually a method. However, it will DWIM about
 
 =item B<get_method ($method_name)>
 
-This will return a CODE reference of the specified C<$method_name>, 
-or return undef if that method does not exist.
+This will return a Class::MOP::Method instance related to the specified 
+C<$method_name>, or return undef if that method does not exist.
+
+The Class::MOP::Method is codifiable, so you can use it like a normal 
+CODE reference, see L<Class::MOP::Method> for more information.
 
 =item B<find_method_by_name ($method_name>
 
@@ -1146,6 +1210,11 @@ It should be noted that any accessor, reader/writer or predicate
 methods which the C<$attribute_meta_object> has will be installed 
 into the class at this time.
 
+B<NOTE>
+If an attribute already exists for C<$attribute_name>, the old one 
+will be removed (as well as removing all it's accessors), and then 
+the new one added.
+
 =item B<has_attribute ($attribute_name)>
 
 Checks to see if this class has an attribute by the name of