bump version and update Changes for release
[gitmo/Moose.git] / lib / Moose / Meta / Role.pm
index bd3e691..f1fd0c7 100644 (file)
@@ -5,11 +5,11 @@ use strict;
 use warnings;
 use metaclass;
 
-use Sub::Name    'subname';
+use Scalar::Util 'blessed';
 use Carp         'confess';
-use Scalar::Util 'blessed', 'reftype';
 
-our $VERSION   = '0.12';
+our $VERSION   = '0.72';
+$VERSION = eval $VERSION;
 our $AUTHORITY = 'cpan:STEVAN';
 
 use Moose::Meta::Class;
@@ -122,6 +122,10 @@ foreach my $action (
 sub add_attribute {
     my $self = shift;
     my $name = shift;
+    unless ( defined $name && $name ) {
+        require Moose;
+        Moose->throw_error("You must provide a name for the attribute");
+    }
     my $attr_desc;
     if (scalar @_ == 1 && ref($_[0]) eq 'HASH') {
         $attr_desc = $_[0];
@@ -215,8 +219,8 @@ $META->add_attribute('override_method_modifiers' => (
 sub add_override_method_modifier {
     my ($self, $method_name, $method) = @_;
     (!$self->has_method($method_name))
-        || confess "Cannot add an override of method '$method_name' " .
-                   "because there is a local version of '$method_name'";
+        || Moose->throw_error("Cannot add an override of method '$method_name' " .
+                   "because there is a local version of '$method_name'");
     $self->get_override_method_modifiers_map->{$method_name} = $method;
 }
 
@@ -241,6 +245,14 @@ sub get_method_modifier_list {
     keys %{$self->$accessor};
 }
 
+sub reset_package_cache_flag  { (shift)->{'_package_cache_flag'} = undef }
+sub update_package_cache_flag {
+    my $self = shift;
+    $self->{'_package_cache_flag'} = Class::MOP::check_package_cache_flag($self->name);
+}
+
+
+
 ## ------------------------------------------------------------------
 ## subroles
 
@@ -252,8 +264,9 @@ __PACKAGE__->meta->add_attribute('roles' => (
 sub add_role {
     my ($self, $role) = @_;
     (blessed($role) && $role->isa('Moose::Meta::Role'))
-        || confess "Roles must be instances of Moose::Meta::Role";
+        || Moose->throw_error("Roles must be instances of Moose::Meta::Role");
     push @{$self->get_roles} => $role;
+    $self->reset_package_cache_flag;
 }
 
 sub calculate_all_roles {
@@ -269,7 +282,7 @@ sub calculate_all_roles {
 sub does_role {
     my ($self, $role_name) = @_;
     (defined $role_name)
-        || confess "You must supply a role name to look for";
+        || Moose->throw_error("You must supply a role name to look for");
     # if we are it,.. then return true
     return 1 if $role_name eq $self->name;
     # otherwise.. check our children
@@ -286,14 +299,28 @@ sub method_metaclass { 'Moose::Meta::Role::Method' }
 
 sub get_method_map {
     my $self = shift;
-    my $map  = {};
+
+    my $current = Class::MOP::check_package_cache_flag($self->name);
+
+    if (defined $self->{'_package_cache_flag'} && $self->{'_package_cache_flag'} == $current) {
+        return $self->{'methods'} ||= {};
+    }
+
+    $self->{_package_cache_flag} = $current;
+
+    my $map  = $self->{'methods'} ||= {};
 
     my $role_name        = $self->name;
     my $method_metaclass = $self->method_metaclass;
 
-    foreach my $symbol ($self->list_all_package_symbols('CODE')) {
+    my $all_code = $self->get_all_package_symbols('CODE');
+
+    foreach my $symbol (keys %{ $all_code }) {
+        my $code = $all_code->{$symbol};
 
-        my $code = $self->get_package_symbol('&' . $symbol);
+        next if exists  $map->{$symbol} &&
+                defined $map->{$symbol} &&
+                        $map->{$symbol}->body == $code;
 
         my ($pkg, $name) = Class::MOP::get_code_info($code);
 
@@ -305,17 +332,30 @@ sub get_method_map {
             # loudly (in the case of Curses.pm) so we
             # just be a little overly cautious here.
             # - SL
-            && eval { no warnings; blessed($pkg->meta) }
+            && eval { no warnings; blessed($pkg->meta) } # FIXME calls meta
             && $pkg->meta->isa('Moose::Meta::Role')) {
             my $role = $pkg->meta->name;
             next unless $self->does_role($role);
         }
         else {
-            next if ($pkg  || '') ne $role_name &&
-                    ($name || '') ne '__ANON__';
+            # NOTE:
+            # in 5.10 constant.pm the constants show up 
+            # as being in the right package, but in pre-5.10
+            # they show up as constant::__ANON__ so we 
+            # make an exception here to be sure that things
+            # work as expected in both.
+            # - SL
+            unless ($pkg eq 'constant' && $name eq '__ANON__') {
+                next if ($pkg  || '') ne $role_name ||
+                        (($name || '') ne '__ANON__' && ($pkg  || '') ne $role_name);
+            }            
         }
-
-        $map->{$symbol} = $method_metaclass->wrap($code);
+        
+        $map->{$symbol} = $method_metaclass->wrap(
+            $code,
+            package_name => $role_name,
+            name         => $name            
+        );
     }
 
     return $map;    
@@ -323,7 +363,7 @@ sub get_method_map {
 
 sub get_method { 
     my ($self, $name) = @_;
-    $self->get_method_map->{$name}
+    $self->get_method_map->{$name};
 }
 
 sub has_method {
@@ -331,6 +371,53 @@ sub has_method {
     exists $self->get_method_map->{$name} ? 1 : 0
 }
 
+# FIXME this is copy-pasted from Class::MOP::Class
+# refactor to inherit from some common base
+sub wrap_method_body {
+    my ( $self, %args ) = @_;
+
+    ('CODE' eq ref $args{body})
+        || Moose->throw_error("Your code block must be a CODE reference");
+
+    $self->method_metaclass->wrap(
+        package_name => $self->name,
+        %args,
+    );
+}
+
+sub add_method {
+    my ($self, $method_name, $method) = @_;
+    (defined $method_name && $method_name)
+    || Moose->throw_error("You must define a method name");
+
+    my $body;
+    if (blessed($method)) {
+        $body = $method->body;
+        if ($method->package_name ne $self->name) {
+            $method = $method->clone(
+                package_name => $self->name,
+                name         => $method_name            
+            ) if $method->can('clone');
+        }
+    }
+    else {
+        $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 $full_method_name = ($self->name . '::' . $method_name);
+    $self->add_package_symbol(
+        { sigil => '&', type => 'CODE', name => $method_name },
+        Class::MOP::subname($full_method_name => $body)
+    );
+
+    $self->update_package_cache_flag; # still valid, since we just added the method to the map, and if it was invalid before that then get_method_map updated it
+}
+
 sub find_method_by_name { (shift)->get_method(@_) }
 
 sub get_method_list {
@@ -339,15 +426,9 @@ sub get_method_list {
 }
 
 sub alias_method {
-    my ($self, $method_name, $method) = @_;
-    (defined $method_name && $method_name)
-        || confess "You must define a method name";
-
-    my $body = (blessed($method) ? $method->body : $method);
-    ('CODE' eq (reftype($body) || ''))
-        || confess "Your code block must be a CODE reference";
+    my $self = shift;
 
-    $self->add_package_symbol("&${method_name}" => $body);
+    $self->add_method(@_);
 }
 
 ## ------------------------------------------------------------------
@@ -358,7 +439,7 @@ sub apply {
     my ($self, $other, @args) = @_;
 
     (blessed($other))
-        || confess "You must pass in an blessed instance";
+        || Moose->throw_error("You must pass in an blessed instance");
         
     if ($other->isa('Moose::Meta::Role')) {
         require Moose::Meta::Role::Application::ToRole;
@@ -375,16 +456,140 @@ sub apply {
 }
 
 sub combine {
-    my ($class, @roles) = @_;
+    my ($class, @role_specs) = @_;
     
     require Moose::Meta::Role::Application::RoleSummation;
-    require Moose::Meta::Role::Composite;    
+    require Moose::Meta::Role::Composite;  
+    
+    my (@roles, %role_params);
+    while (@role_specs) {
+        my ($role, $params) = @{ splice @role_specs, 0, 1 };
+        push @roles => $role->meta;
+        next unless defined $params;
+        $role_params{$role} = $params; 
+    }
     
     my $c = Moose::Meta::Role::Composite->new(roles => \@roles);
-    Moose::Meta::Role::Application::RoleSummation->new->apply($c);
+    Moose::Meta::Role::Application::RoleSummation->new(
+        role_params => \%role_params
+    )->apply($c);
+    
     return $c;
 }
 
+sub create {
+    my ( $role, $package_name, %options ) = @_;
+
+    $options{package} = $package_name;
+
+    (ref $options{attributes} eq 'HASH')
+        || confess "You must pass a HASH ref of attributes"
+            if exists $options{attributes};
+
+    (ref $options{methods} eq 'HASH')
+        || confess "You must pass a HASH ref of methods"
+            if exists $options{methods};
+
+    $role->SUPER::create(%options);
+
+    my (%initialize_options) = %options;
+    delete @initialize_options{qw(
+        package
+        attributes
+        methods
+        version
+        authority
+    )};
+
+    my $meta = $role->initialize( $package_name => %initialize_options );
+
+    # FIXME totally lame
+    $meta->add_method('meta' => sub {
+        $role->initialize(ref($_[0]) || $_[0]);
+    });
+
+    if (exists $options{attributes}) {
+        foreach my $attribute_name (keys %{$options{attributes}}) {
+            my $attr = $options{attributes}->{$attribute_name};
+            $meta->add_attribute($attribute_name => $attr);
+        }
+    }
+
+    if (exists $options{methods}) {
+        foreach my $method_name (keys %{$options{methods}}) {
+            $meta->add_method($method_name, $options{methods}->{$method_name});
+        }
+    }
+
+    Class::MOP::weaken_metaclass($meta->name)
+        if $meta->is_anon_role;
+
+    return $meta;
+}
+
+# anonymous roles. most of it is copied straight out of Class::MOP::Class.
+# an intrepid hacker might find great riches if he unifies this code with that
+# code in Class::MOP::Module or Class::MOP::Package
+{
+    # 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_ROLE_SERIAL = 0;
+
+    # 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_ROLE_PREFIX = 'Moose::Meta::Role::__ANON__::SERIAL::';
+
+    sub is_anon_role {
+        my $self = shift;
+        no warnings 'uninitialized';
+        $self->name =~ /^$ANON_ROLE_PREFIX/;
+    }
+
+    sub create_anon_role {
+        my ($role, %options) = @_;
+        my $package_name = $ANON_ROLE_PREFIX . ++$ANON_ROLE_SERIAL;
+        return $role->create($package_name, %options);
+    }
+
+    # NOTE:
+    # this will only get called for
+    # anon-roles, 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 if Class::MOP::in_global_destruction(); # it'll happen soon anyway and this just makes things more complicated
+
+        no warnings 'uninitialized';
+        return unless $self->name =~ /^$ANON_ROLE_PREFIX/;
+
+        # XXX: is this necessary for us? I don't understand what it's doing
+        # -sartak
+
+        # Moose does a weird thing where it replaces the metaclass for
+        # class when fixing metaclass incompatibility. In that case,
+        # we don't want to clean out the namespace now. We can detect
+        # that because Moose will explicitly update the singleton
+        # cache in Class::MOP.
+        #my $current_meta = Class::MOP::get_metaclass_by_name($self->name);
+        #return if $current_meta ne $self;
+
+        my ($serial_id) = ($self->name =~ /^$ANON_ROLE_PREFIX(\d+)/);
+        no strict 'refs';
+        foreach my $key (keys %{$ANON_ROLE_PREFIX . $serial_id}) {
+            delete ${$ANON_ROLE_PREFIX . $serial_id}{$key};
+        }
+        delete ${'main::' . $ANON_ROLE_PREFIX}{$serial_id . '::'};
+    }
+}
+
 #####################################################################
 ## NOTE:
 ## This is Moose::Meta::Role as defined by Moose (plus the use of 
@@ -533,6 +738,8 @@ probably not that much really).
 
 =item B<apply>
 
+=item B<apply_to_metaclass_instance>
+
 =item B<combine>
 
 =back
@@ -581,6 +788,10 @@ probably not that much really).
 
 =item B<has_method>
 
+=item B<add_method>
+
+=item B<wrap_method_body>
+
 =item B<alias_method>
 
 =item B<get_method_list>
@@ -673,6 +884,16 @@ probably not that much really).
 
 =back
 
+=over 4
+
+=item B<create>
+
+=item B<create_anon_role>
+
+=item B<is_anon_role>
+
+=back
+
 =head1 BUGS
 
 All complex software has bugs lurking in it, and this module is no
@@ -685,7 +906,7 @@ Stevan Little E<lt>stevan@iinteractive.comE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2006-2008 by Infinity Interactive, Inc.
+Copyright 2006-2009 by Infinity Interactive, Inc.
 
 L<http://www.iinteractive.com>