cleanup
[gitmo/Class-MOP.git] / lib / Class / MOP / Class.pm
index a95d0b8..ceec1b4 100644 (file)
@@ -5,11 +5,13 @@ use strict;
 use warnings;
 
 use Carp         'confess';
-use Scalar::Util 'blessed', 'reftype';
+use Scalar::Util 'blessed', 'reftype', 'weaken';
 use Sub::Name    'subname';
 use B            'svref_2object';
 
-our $VERSION = '0.14';
+our $VERSION = '0.15';
+
+use base 'Class::MOP::Module';
 
 use Class::MOP::Instance;
 
@@ -17,9 +19,18 @@ use Class::MOP::Instance;
 
 sub meta { Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) }
 
+# Class globals ...
+
+# NOTE:
+# we need a sufficiently annoying prefix
+# this should suffice for now, this is 
+# used in a couple of places below, so 
+# need to put it up here for now.
+my $ANON_CLASS_PREFIX = 'Class::MOP::Class::__ANON__::SERIAL::';
+
 # Creation
 
-#{
+{
     # Metaclasses are singletons, so we cache them here.
     # there is no need to worry about destruction though
     # because they should die only when the program dies.
@@ -40,6 +51,15 @@ sub meta { Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) }
         $class->construct_class_instance(':package' => $package_name, @_);
     }
     
+    sub reinitialize {
+        my $class        = shift;
+        my $package_name = shift;
+        (defined $package_name && $package_name && !blessed($package_name))
+            || confess "You must pass a package name and it cannot be blessed";    
+        $METAS{$package_name} = undef;
+        $class->construct_class_instance(':package' => $package_name, @_);
+    }       
+    
     # NOTE: (meta-circularity) 
     # this is a special form of &construct_instance 
     # (see below), which is used to construct class
@@ -68,7 +88,7 @@ sub meta { Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) }
                 '%:attributes'          => {},
                 '$:attribute_metaclass' => $options{':attribute_metaclass'} || 'Class::MOP::Attribute',
                 '$:method_metaclass'    => $options{':method_metaclass'}    || 'Class::MOP::Method',
-                '$:instance_metaclass'  => $options{':instance_metaclass'}  || 'Class::MOP::Instance',    
+                '$:instance_metaclass'  => $options{':instance_metaclass'}  || 'Class::MOP::Instance',
             } => $class;
         }
         else {
@@ -76,18 +96,25 @@ sub meta { Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) }
             # it is safe to use meta here because
             # class will always be a subclass of 
             # Class::MOP::Class, which defines meta
-            $meta = bless $class->meta->construct_instance(%options) => $class
+            $meta = $class->meta->construct_instance(%options)
         }
         # and check the metaclass compatibility
         $meta->check_metaclass_compatability();
         $METAS{$package_name} = $meta;
-    }
+        # NOTE:
+        # we need to weaken any anon classes
+        # so that they can call DESTROY properly
+        weaken($METAS{$package_name})
+            if $package_name =~ /^$ANON_CLASS_PREFIX/;
+        $meta;        
+    } 
     
     sub check_metaclass_compatability {
         my $self = shift;
 
         # this is always okay ...
-        return if blessed($self) eq 'Class::MOP::Class';
+        return if blessed($self)            eq 'Class::MOP::Class'   && 
+                  $self->instance_metaclass eq 'Class::MOP::Instance';
 
         my @class_list = $self->class_precedence_list;
         shift @class_list; # shift off $self->name
@@ -98,9 +125,51 @@ sub meta { Class::MOP::Class->initialize(blessed($_[0]) || $_[0]) }
                 || confess $self->name . "->meta => (" . (blessed($self)) . ")" . 
                            " is not compatible with the " . 
                            $class_name . "->meta => (" . (blessed($meta)) . ")";
+            # NOTE:
+            # we also need to check that instance metaclasses
+            # are compatabile in the same the class.
+            ($self->instance_metaclass->isa($meta->instance_metaclass))
+                || confess $self->name . "->meta => (" . ($self->instance_metaclass) . ")" . 
+                           " is not compatible with the " . 
+                           $class_name . "->meta => (" . ($meta->instance_metaclass) . ")";                           
         }        
+    } 
+}
+
+## ANON classes
+
+{
+    # NOTE:
+    # this should be sufficient, if you have a 
+    # use case where it is not, write a test and 
+    # I will change it.
+    my $ANON_CLASS_SERIAL = 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);
     }
-#}
+}    
+
+# NOTE:
+# this will only get called for 
+# anon-classes, all other calls 
+# are assumed to occur during 
+# global destruction and so don't
+# really need to be handled explicitly
+sub DESTROY {
+    my $self = shift;
+    return unless $self->name =~ /^$ANON_CLASS_PREFIX/;
+    my ($serial_id) = ($self->name =~ /^$ANON_CLASS_PREFIX(\d+)/);
+    no strict 'refs';     
+    foreach my $key (keys %{$ANON_CLASS_PREFIX . $serial_id}) {
+        delete ${$ANON_CLASS_PREFIX . $serial_id}{$key};
+    }
+    delete ${'main::' . $ANON_CLASS_PREFIX}{$serial_id . '::'};        
+}
+
+# creating classes with MOP ...
 
 sub create {
     my ($class, $package_name, $package_version, %options) = @_;
@@ -114,7 +183,7 @@ sub create {
     my $meta = $class->initialize($package_name);
     
     $meta->add_method('meta' => sub { 
-        Class::MOP::Class->initialize(blessed($_[0]) || $_[0]);
+        $class->initialize(blessed($_[0]) || $_[0]);
     });
     
     $meta->superclasses(@{$options{superclasses}})
@@ -137,27 +206,12 @@ sub create {
     return $meta;
 }
 
-{
-    # NOTE:
-    # this should be sufficient, if you have a 
-    # use case where it is not, write a test and 
-    # I will change it.
-    my $ANON_CLASS_SERIAL = 0;
-    
-    sub create_anon_class {
-        my ($class, %options) = @_;   
-        my $package_name = 'Class::MOP::Class::__ANON__::SERIAL::' . ++$ANON_CLASS_SERIAL;
-        return $class->create($package_name, '0.00', %options);
-    }
-}
-
 ## Attribute readers
 
 # NOTE:
 # all these attribute readers will be bootstrapped 
 # away in the Class::MOP bootstrap section
 
-sub name                { $_[0]->{'$:package'}             }
 sub get_attribute_map   { $_[0]->{'%:attributes'}          }
 sub attribute_metaclass { $_[0]->{'$:attribute_metaclass'} }
 sub method_metaclass    { $_[0]->{'$:method_metaclass'}    }
@@ -181,7 +235,6 @@ sub construct_instance {
     my ($class, %params) = @_;
     my $meta_instance = $class->get_meta_instance();
     my $instance = $meta_instance->create_instance();
-    $meta_instance->initialize_all_slots($instance);
     foreach my $attr ($class->compute_all_applicable_attributes()) {
         $attr->initialize_instance_slot($meta_instance, $instance, \%params);
     }
@@ -206,27 +259,22 @@ sub clone_object {
     # Class::MOP::Class singletons here, they 
     # should not be cloned.
     return $instance if $instance->isa('Class::MOP::Class');   
-    bless $class->clone_instance($instance, @_) => blessed($instance);
+    $class->clone_instance($instance, @_);
 }
 
 sub clone_instance {
     my ($class, $instance, %params) = @_;
     (blessed($instance))
         || confess "You can only clone instances, \$self is not a blessed instance";
-    my $clone = { %$instance, %params }; 
+    my $meta_instance = $class->get_meta_instance();
+    my $clone = $meta_instance->clone_instance($instance);        
+    foreach my $key (keys %params) {
+        next unless $meta_instance->is_valid_slot($key);
+        $meta_instance->set_slot_value($clone, $key, $params{$key});
+    }
     return $clone;    
 }
 
-# Informational 
-
-# &name should be here too, but it is above
-# because it gets bootstrapped away
-
-sub version {  
-    my $self = shift;
-    ${$self->get_package_variable('$VERSION')};
-}
-
 # Inheritance
 
 sub superclasses {
@@ -235,6 +283,13 @@ sub superclasses {
     if (@_) {
         my @supers = @_;
         @{$self->name . '::ISA'} = @supers;
+        # NOTE:
+        # we need to check the metaclass 
+        # compatability here so that we can 
+        # be sure that the superclass is 
+        # not potentially creating an issues 
+        # we don't know about
+        $self->check_metaclass_compatability();
     }
     @{$self->name . '::ISA'};
 }
@@ -251,11 +306,7 @@ sub class_precedence_list {
     (
         $self->name, 
         map { 
-            # OPTIMIZATION NOTE:
-            # we grab the metaclass from the %METAS 
-            # hash here to save the initialize() call
-            # if we can, but it is not always possible            
-            ($METAS{$_} || $self->initialize($_))->class_precedence_list()
+            $self->initialize($_)->class_precedence_list()
         } $self->superclasses()
     );   
 }
@@ -285,23 +336,22 @@ sub add_method {
         my $method = $self->get_method($method_name);
         # if we dont have local ...
         unless ($method) {
-            # make sure this method even exists ...
-            ($self->find_next_method_by_name($method_name))
+            # try to find the next 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";
-            # 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);
-        }
-        
-        # now make sure we wrap it properly 
-        # (if it isnt already)
-        unless ($method->isa('Class::MOP::Method::Wrapped')) {
+            # and now make sure to wrap it 
+            # even if it is already wrapped
+            # because we need a new sub ref
             $method = Class::MOP::Method::Wrapped->wrap($method);
-            $self->add_method($method_name => $method); 
-        }       
+        }
+        else {
+            # now make sure we wrap it properly 
+            $method = Class::MOP::Method::Wrapped->wrap($method)
+                unless $method->isa('Class::MOP::Method::Wrapped');  
+        }    
+        $self->add_method($method_name => $method);        
         return $method;
     };
 
@@ -406,7 +456,7 @@ sub remove_method {
 sub get_method_list {
     my $self = shift;
     no strict 'refs';
-    grep { $self->has_method($_) } %{$self->name . '::'};
+    grep { $self->has_method($_) } keys %{$self->name . '::'};
 }
 
 sub compute_all_applicable_methods {
@@ -510,12 +560,8 @@ sub get_attribute {
     my ($self, $attribute_name) = @_;
     (defined $attribute_name && $attribute_name)
         || confess "You must define an attribute name";
-    # OPTIMIZATION NOTE:
-    # we used to say `if $self->has_attribute($attribute_name)` 
-    # here, but since get_attribute is called so often, we 
-    # eliminate the function call here
-    return $self->{'%:attributes'}->{$attribute_name} 
-        if exists $self->{'%:attributes'}->{$attribute_name};   
+    return $self->get_attribute_map->{$attribute_name} 
+        if $self->has_attribute($attribute_name);   
     return; 
 } 
 
@@ -533,12 +579,7 @@ sub remove_attribute {
 
 sub get_attribute_list {
     my $self = shift;
-    # OPTIMIZATION NOTE:
-    # We don't use get_attribute_map here because 
-    # we ask for the attribute list quite often 
-    # in compute_all_applicable_attributes, so 
-    # eliminating the function call helps 
-    keys %{$self->{'%:attributes'}};
+    keys %{$self->get_attribute_map};
 } 
 
 sub compute_all_applicable_attributes {
@@ -553,10 +594,7 @@ sub compute_all_applicable_attributes {
         next if $seen_class{$class};
         $seen_class{$class}++;
         # fetch the meta-class ...
-        # OPTIMIZATION NOTE:
-        # we grab the metaclass from the %METAS 
-        # hash here to save the initialize() call
-        my $meta = $METAS{$class};
+        my $meta = $self->initialize($class);
         foreach my $attr_name ($meta->get_attribute_list()) { 
             next if exists $seen_attr{$attr_name};
             $seen_attr{$attr_name}++;
@@ -584,69 +622,13 @@ sub find_attribute_by_name {
     return;
 }
 
-# Class attributes
-
-sub add_package_variable {
-    my ($self, $variable, $initial_value) = @_;
-    (defined $variable && $variable =~ /^[\$\@\%]/)
-        || confess "variable name does not have a sigil";
-    
-    my ($sigil, $name) = ($variable =~ /^(.)(.*)$/); 
-    if (defined $initial_value) {
-        no strict 'refs';
-        *{$self->name . '::' . $name} = $initial_value;
-    }
-    else {
-        my $e;
-        {        
-            # NOTE:
-            # We HAVE to localize $@ or all 
-            # hell breaks loose. It is not 
-            # good, believe me, not good.
-            local $@;
-            eval $sigil . $self->name . '::' . $name;
-            $e = $@ if $@;            
-        }
-        confess "Could not create package variable ($variable) because : $e" if $e;
-    }
-}
-
-sub has_package_variable {
-    my ($self, $variable) = @_;
-    (defined $variable && $variable =~ /^[\$\@\%]/)
-        || confess "variable name does not have a sigil";
-    my ($sigil, $name) = ($variable =~ /^(.)(.*)$/); 
-    no strict 'refs';
-    defined ${$self->name . '::'}{$name} ? 1 : 0;
-}
+## Class closing
 
-sub get_package_variable {
-    my ($self, $variable) = @_;
-    (defined $variable && $variable =~ /^[\$\@\%]/)
-        || confess "variable name does not have a sigil";
-    my ($sigil, $name) = ($variable =~ /^(.)(.*)$/); 
-    my ($ref, $e);
-    {
-        # NOTE:
-        # We HAVE to localize $@ or all 
-        # hell breaks loose. It is not 
-        # good, believe me, not good.
-        local $@;        
-        $ref = eval '\\' . $sigil . $self->name . '::' . $name;
-        $e = $@ if $@;
-    }
-    confess "Could not get the package variable ($variable) because : $e" if $e;    
-    # if we didn't die, then we can return it
-    return $ref;
-}
+sub is_mutable   { 1 }
+sub is_immutable { 0 }
 
-sub remove_package_variable {
-    my ($self, $variable) = @_;
-    (defined $variable && $variable =~ /^[\$\@\%]/)
-        || confess "variable name does not have a sigil";
-    my ($sigil, $name) = ($variable =~ /^(.)(.*)$/); 
-    no strict 'refs';
-    delete ${$self->name . '::'}{$name};
+sub make_immutable {
+    return Class::MOP::Class::Immutable->make_metaclass_immutable(@_);
 }
 
 1;
@@ -762,11 +744,17 @@ This will create an anonymous class, it works much like C<create> but
 it does not need a C<$package_name>. Instead it will create a suitably 
 unique package name for you to stash things into.
 
-=item B<initialize ($package_name)>
+=item B<initialize ($package_name, %options)>
 
 This initializes and returns returns a B<Class::MOP::Class> object 
 for a given a C<$package_name>.
 
+=item B<reinitialize ($package_name, %options)>
+
+This removes the old metaclass, and creates a new one in it's place.
+Do B<not> use this unless you really know what you are doing, it could 
+very easily make a very large mess of your program. 
+
 =item B<construct_class_instance (%options)>
 
 This will construct an instance of B<Class::MOP::Class>, it is 
@@ -1183,7 +1171,7 @@ the creation and inspection of package scoped variables.
 
 =over 4
 
-=item B<add_package_variable ($variable_name, ?$initial_value)>
+=item B<add_package_symbol ($variable_name, ?$initial_value)>
 
 Given a C<$variable_name>, which must contain a leading sigil, this 
 method will create that variable within the package which houses the 
@@ -1191,22 +1179,34 @@ class. It also takes an optional C<$initial_value>, which must be a
 reference of the same type as the sigil of the C<$variable_name> 
 implies.
 
-=item B<get_package_variable ($variable_name)>
+=item B<get_package_symbol ($variable_name)>
 
 This will return a reference to the package variable in 
 C<$variable_name>. 
 
-=item B<has_package_variable ($variable_name)>
+=item B<has_package_symbol ($variable_name)>
 
 Returns true (C<1>) if there is a package variable defined for 
 C<$variable_name>, and false (C<0>) otherwise.
 
-=item B<remove_package_variable ($variable_name)>
+=item B<remove_package_symbol ($variable_name)>
 
 This will attempt to remove the package variable at C<$variable_name>.
 
 =back
 
+=head2 Class closing
+
+=over 4
+
+=item B<is_mutable>
+
+=item B<is_immutable>
+
+=item B<make_immutable>
+
+=back
+
 =head1 AUTHOR
 
 Stevan Little E<lt>stevan@iinteractive.comE<gt>
@@ -1220,4 +1220,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. 
 
-=cutchistian
+=cut