basic implementation of preserving attrs/methods across reinitialization
Jesse Luehrs [Sun, 26 Sep 2010 09:04:53 +0000 (04:04 -0500)]
lib/Class/MOP/Class.pm
t/010_self_introspection.t
t/049_metaclass_reinitialize.t
xt/author/pod_coverage.t

index 628fee5..24621c8 100644 (file)
@@ -46,6 +46,19 @@ sub initialize {
         || $class->_construct_class_instance(package => $package_name, @_);
 }
 
+sub reinitialize {
+    my ( $class, @args ) = @_;
+    unshift @args, "package" if @args % 2;
+    my %options = @args;
+    my $old_metaclass = blessed($options{package})
+        ? $options{package}
+        : Class::MOP::get_metaclass_by_name($options{package});
+    my $new_metaclass = $class->SUPER::reinitialize(@args);
+    $new_metaclass->_restore_metaobjects_from($old_metaclass)
+        if $old_metaclass;
+    return $new_metaclass;
+}
+
 # NOTE: (meta-circularity)
 # this is a special form of _construct_instance
 # (see below), which is used to construct class
@@ -394,6 +407,84 @@ sub _fix_single_metaclass_incompatibility {
     }
 }
 
+sub _get_associated_single_metaclass {
+    my $self = shift;
+    my ($single_meta_name) = @_;
+
+    my $current_single_meta_name;
+    if ($single_meta_name->isa('Class::MOP::Method')) {
+        $current_single_meta_name = $self->method_metaclass;
+    }
+    elsif ($single_meta_name->isa('Class::MOP::Attribute')) {
+        $current_single_meta_name = $self->attribute_metaclass;
+    }
+    else {
+        confess "Can't make $single_meta_name compatible, it isn't an "
+              . "attribute or method metaclass.";
+    }
+
+    return $current_single_meta_name;
+}
+
+sub _get_compatible_single_metaclass_by_subclassing {
+    my $self = shift;
+    my ($single_meta_name) = @_;
+
+    my $current_single_meta_name = $self->_get_associated_single_metaclass($single_meta_name);
+
+    if ($single_meta_name->isa($current_single_meta_name)) {
+        return $single_meta_name;
+    }
+    elsif ($current_single_meta_name->isa($single_meta_name)) {
+        return $current_single_meta_name;
+    }
+
+    return;
+}
+
+sub _get_compatible_single_metaclass {
+    my $self = shift;
+    my ($single_meta_name) = @_;
+
+    return $self->_get_compatible_single_metaclass_by_subclassing($single_meta_name);
+}
+
+sub _make_metaobject_compatible {
+    my $self = shift;
+    my ($object) = @_;
+
+    my $new_metaclass = $self->_get_compatible_single_metaclass(blessed($object));
+
+    if (!defined($new_metaclass)) {
+        confess "Can't make $object compatible with metaclass "
+              . $self->_get_associated_single_metaclass(blessed($object));
+    }
+
+    # XXX: is this sufficient? i think so... we should never lose attributes
+    # by this process
+    bless($object, $new_metaclass)
+        if blessed($object) ne $new_metaclass;
+
+    return $object;
+}
+
+sub _restore_metaobjects_from {
+    my $self = shift;
+    my ($old_meta) = @_;
+
+    for my $method ($old_meta->_get_local_methods) {
+        $self->_make_metaobject_compatible($method);
+        $self->add_method($method->name => $method);
+    }
+
+    for my $attr (sort { $a->insertion_order <=> $b->insertion_order }
+                       map { $old_meta->get_attribute($_) }
+                           $old_meta->get_attribute_list) {
+        $self->_make_metaobject_compatible($attr);
+        $self->add_attribute($attr);
+    }
+}
+
 ## ANON classes
 
 {
index f9d9a09..e3e9c1b 100644 (file)
@@ -53,7 +53,7 @@ my @class_mop_class_methods = qw(
 
     is_pristine
 
-    initialize create
+    initialize reinitialize create
 
     update_package_cache_flag
     reset_package_cache_flag
@@ -79,6 +79,12 @@ my @class_mop_class_methods = qw(
     _can_fix_metaclass_incompatibility_by_subclassing
     _can_fix_metaclass_incompatibility
 
+    _get_associated_single_metaclass
+    _get_compatible_single_metaclass_by_subclassing
+    _get_compatible_single_metaclass
+    _make_metaobject_compatible
+    _restore_metaobjects_from
+
     add_meta_instance_dependencies remove_meta_instance_dependencies update_meta_instance_dependencies
     add_dependent_meta_instance remove_dependent_meta_instance
     invalidate_meta_instances invalidate_meta_instance
index a9c0e26..423a49c 100644 (file)
@@ -8,6 +8,7 @@ use Test::Exception;
     package Foo;
     use metaclass;
     sub foo {}
+    Foo->meta->add_attribute('bar');
 }
 
 sub check_meta_sanity {
@@ -15,6 +16,7 @@ sub check_meta_sanity {
     isa_ok($meta, 'Class::MOP::Class');
     is($meta->name, 'Foo');
     ok($meta->has_method('foo'));
+    ok($meta->has_attribute('bar'));
 }
 
 can_ok('Foo', 'meta');
index be543c4..4a7bfdf 100644 (file)
@@ -45,6 +45,7 @@ my %trustme = (
         'create_meta_instance',
         'reset_package_cache_flag',
         'update_package_cache_flag',
+        'reinitialize',
 
         # doc'd with rebless_instance
         'rebless_instance_away',