add with qw( Role1 Role2 ) support
[gitmo/Mouse.git] / lib / Mouse / Meta / Role.pm
1 #!/usr/bin/env perl
2 package Mouse::Meta::Role;
3 use strict;
4 use warnings;
5 use Carp 'confess';
6
7 do {
8     my %METACLASS_CACHE;
9
10     # because Mouse doesn't introspect existing classes, we're forced to
11     # only pay attention to other Mouse classes
12     sub _metaclass_cache {
13         my $class = shift;
14         my $name  = shift;
15         return $METACLASS_CACHE{$name};
16     }
17
18     sub initialize {
19         my $class = shift;
20         my $name  = shift;
21         $METACLASS_CACHE{$name} = $class->new(name => $name)
22             if !exists($METACLASS_CACHE{$name});
23         return $METACLASS_CACHE{$name};
24     }
25 };
26
27 sub new {
28     my $class = shift;
29     my %args  = @_;
30
31     $args{attributes}       ||= {};
32     $args{required_methods} ||= [];
33     $args{roles}            ||= [];
34
35     bless \%args, $class;
36 }
37
38 sub name { $_[0]->{name} }
39
40 sub add_required_methods {
41     my $self = shift;
42     my @methods = @_;
43     push @{$self->{required_methods}}, @methods;
44 }
45
46 sub add_attribute {
47     my $self = shift;
48     my $name = shift;
49     my $spec = shift;
50     $self->{attributes}->{$name} = $spec;
51 }
52
53 sub has_attribute { exists $_[0]->{attributes}->{$_[1]}  }
54 sub get_attribute_list { keys %{ $_[0]->{attributes} } }
55 sub get_attribute { $_[0]->{attributes}->{$_[1]} }
56
57 # copied from Class::Inspector
58 sub get_method_list {
59     my $self = shift;
60     my $name = $self->name;
61
62     no strict 'refs';
63     # Get all the CODE symbol table entries
64     my @functions = grep !/^meta$/,
65       grep { /\A[^\W\d]\w*\z/o }
66       grep { defined &{"${name}::$_"} }
67       keys %{"${name}::"};
68     wantarray ? @functions : \@functions;
69 }
70
71 sub apply {
72     my $self  = shift;
73     my $selfname = $self->name;
74     my $class = shift;
75     my $classname = $class->name;
76     my %args  = @_;
77
78     if ($class->isa('Mouse::Meta::Class')) {
79         for my $name (@{$self->{required_methods}}) {
80             unless ($classname->can($name)) {
81                 confess "'$selfname' requires the method '$name' to be implemented by '$classname'";
82             }
83         }
84     }
85
86     {
87         no strict 'refs';
88         for my $name ($self->get_method_list) {
89             next if $name eq 'has' || $name eq 'requires' || $name eq 'meta' || $name eq 'with' || $name eq 'around' || $name eq 'before' || $name eq 'after' || $name eq 'blessed' || $name eq 'extends' || $name eq 'confess' || $name eq 'excludes';
90
91             if ($classname->can($name)) {
92                 # XXX what's Moose's behavior?
93                 #next;
94             } else {
95                 *{"${classname}::${name}"} = *{"${selfname}::${name}"};
96             }
97             if ($args{alias} && $args{alias}->{$name}) {
98                 my $dstname = $args{alias}->{$name};
99                 unless ($classname->can($dstname)) {
100                     *{"${classname}::${dstname}"} = *{"${selfname}::${name}"};
101                 }
102             }
103         }
104     }
105
106     if ($class->isa('Mouse::Meta::Class')) {
107         # apply role to class
108         for my $name ($self->get_attribute_list) {
109             next if $class->has_attribute($name);
110             my $spec = $self->get_attribute($name);
111             Mouse::Meta::Attribute->create($class, $name, %$spec);
112         }
113     } else {
114         # apply role to role
115         # XXX Room for speed improvement
116         for my $name ($self->get_attribute_list) {
117             next if $class->has_attribute($name);
118             my $spec = $self->get_attribute($name);
119             $class->add_attribute($name, $spec);
120         }
121     }
122
123     # XXX Room for speed improvement in role to role
124     for my $modifier_type (qw/before after around/) {
125         my $add_method = "add_${modifier_type}_method_modifier";
126         my $modified = $self->{"${modifier_type}_method_modifiers"};
127
128         for my $method_name (keys %$modified) {
129             for my $code (@{ $modified->{$method_name} }) {
130                 $class->$add_method($method_name => $code);
131             }
132         }
133     }
134
135     # append roles
136     push @{ $class->roles }, $self, @{ $self->roles };
137 }
138
139 sub combine_apply {
140     my(undef, $class, @roles) = @_;
141     my $classname = $class->name;
142
143     if ($class->isa('Mouse::Meta::Class')) {
144         for my $role_spec (@roles) {
145             my $self = $role_spec->[0]->meta;
146             for my $name (@{$self->{required_methods}}) {
147                 unless ($classname->can($name)) {
148                     my $method_required = 0;
149                     for my $role (@roles) {
150                         $method_required = 1 if $self->name ne $role->[0] && $role->[0]->can($name);
151                     }
152                     confess "'".$self->name."' requires the method '$name' to be implemented by '$classname'"
153                         unless $method_required;
154                 }
155             }
156         }
157     }
158
159     {
160         no strict 'refs';
161         for my $role_spec (@roles) {
162             my $self = $role_spec->[0]->meta;
163             my $selfname = $self->name;
164             my %args = %{ $role_spec->[1] };
165             for my $name ($self->get_method_list) {
166                 next if $name eq 'has' || $name eq 'requires' || $name eq 'meta' || $name eq 'with' || $name eq 'around' || $name eq 'before' || $name eq 'after' || $name eq 'blessed' || $name eq 'extends' || $name eq 'confess' || $name eq 'excludes';
167
168                 if ($classname->can($name)) {
169                     # XXX what's Moose's behavior?
170                     #next;
171                 } else {
172                     *{"${classname}::${name}"} = *{"${selfname}::${name}"};
173                 }
174                 if ($args{alias} && $args{alias}->{$name}) {
175                     my $dstname = $args{alias}->{$name};
176                     unless ($classname->can($dstname)) {
177                         *{"${classname}::${dstname}"} = *{"${selfname}::${name}"};
178                     }
179                 }
180             }
181         }
182     }
183
184
185     if ($class->isa('Mouse::Meta::Class')) {
186         # apply role to class
187         for my $role_spec (@roles) {
188             my $self = $role_spec->[0]->meta;
189             for my $name ($self->get_attribute_list) {
190                 next if $class->has_attribute($name);
191                 my $spec = $self->get_attribute($name);
192                 Mouse::Meta::Attribute->create($class, $name, %$spec);
193             }
194         }
195     } else {
196         # apply role to role
197         # XXX Room for speed improvement
198         for my $role_spec (@roles) {
199             my $self = $role_spec->[0]->meta;
200             for my $name ($self->get_attribute_list) {
201                 next if $class->has_attribute($name);
202                 my $spec = $self->get_attribute($name);
203                 $class->add_attribute($name, $spec);
204             }
205         }
206     }
207
208     # XXX Room for speed improvement in role to role
209     for my $modifier_type (qw/before after around/) {
210         my $add_method = "add_${modifier_type}_method_modifier";
211         for my $role_spec (@roles) {
212             my $self = $role_spec->[0]->meta;
213             my $modified = $self->{"${modifier_type}_method_modifiers"};
214
215             for my $method_name (keys %$modified) {
216                 for my $code (@{ $modified->{$method_name} }) {
217                     $class->$add_method($method_name => $code);
218                 }
219             }
220         }
221     }
222
223     # append roles
224     my %role_apply_cache;
225     my @apply_roles;
226     for my $role_spec (@roles) {
227         my $self = $role_spec->[0]->meta;
228         push @apply_roles, $self unless $role_apply_cache{$self}++;
229         for my $role ($self->roles) {
230             push @apply_roles, $role unless $role_apply_cache{$role}++;
231         }
232     }
233 }
234
235 for my $modifier_type (qw/before after around/) {
236     no strict 'refs';
237     *{ __PACKAGE__ . '::' . "add_${modifier_type}_method_modifier" } = sub {
238         my ($self, $method_name, $method) = @_;
239
240         push @{ $self->{"${modifier_type}_method_modifiers"}->{$method_name} },
241             $method;
242     };
243
244     *{ __PACKAGE__ . '::' . "get_${modifier_type}_method_modifiers" } = sub {
245         my ($self, $method_name, $method) = @_;
246         @{ $self->{"${modifier_type}_method_modifiers"}->{$method_name} || [] }
247     };
248 }
249
250 sub roles { $_[0]->{roles} }
251
252 1;
253