more-tests
[gitmo/Moose.git] / lib / Moose / Meta / Role.pm
1
2 package Moose::Meta::Role;
3
4 use strict;
5 use warnings;
6 use metaclass;
7
8 use Carp         'confess';
9 use Scalar::Util 'blessed';
10
11 use Moose::Meta::Class;
12
13 our $VERSION = '0.03';
14
15 ## Attributes
16
17 ## the meta for the role package
18
19 __PACKAGE__->meta->add_attribute('_role_meta' => (
20     reader   => '_role_meta',
21     init_arg => ':role_meta'
22 ));
23
24 ## roles
25
26 __PACKAGE__->meta->add_attribute('roles' => (
27     reader  => 'get_roles',
28     default => sub { [] }
29 ));
30
31 ## excluded roles
32
33 __PACKAGE__->meta->add_attribute('excluded_roles_map' => (
34     reader  => 'get_excluded_roles_map',
35     default => sub { {} }
36 ));
37
38 ## attributes
39
40 __PACKAGE__->meta->add_attribute('attribute_map' => (
41     reader   => 'get_attribute_map',
42     default  => sub { {} }
43 ));
44
45 ## required methods
46
47 __PACKAGE__->meta->add_attribute('required_methods' => (
48     reader  => 'get_required_methods_map',
49     default => sub { {} }
50 ));
51
52 ## method modifiers
53
54 __PACKAGE__->meta->add_attribute('before_method_modifiers' => (
55     reader  => 'get_before_method_modifiers_map',
56     default => sub { {} } # (<name> => [ (CODE) ])
57 ));
58
59 __PACKAGE__->meta->add_attribute('after_method_modifiers' => (
60     reader  => 'get_after_method_modifiers_map',
61     default => sub { {} } # (<name> => [ (CODE) ])
62 ));
63
64 __PACKAGE__->meta->add_attribute('around_method_modifiers' => (
65     reader  => 'get_around_method_modifiers_map',
66     default => sub { {} } # (<name> => [ (CODE) ])
67 ));
68
69 __PACKAGE__->meta->add_attribute('override_method_modifiers' => (
70     reader  => 'get_override_method_modifiers_map',
71     default => sub { {} } # (<name> => CODE) 
72 ));
73
74 ## Methods 
75
76 sub new {
77     my $class   = shift;
78     my %options = @_;
79     $options{':role_meta'} = Moose::Meta::Class->initialize(
80         $options{role_name},
81         ':method_metaclass' => 'Moose::Meta::Role::Method'
82     ) unless defined $options{':role_meta'} && 
83              $options{':role_meta'}->isa('Moose::Meta::Class');
84     my $self = $class->meta->new_object(%options);
85     return $self;
86 }
87
88 ## subroles
89
90 sub add_role {
91     my ($self, $role) = @_;
92     (blessed($role) && $role->isa('Moose::Meta::Role'))
93         || confess "Roles must be instances of Moose::Meta::Role";
94     push @{$self->get_roles} => $role;
95 }
96
97 sub does_role {
98     my ($self, $role_name) = @_;
99     (defined $role_name)
100         || confess "You must supply a role name to look for";
101     # if we are it,.. then return true
102     return 1 if $role_name eq $self->name;
103     # otherwise.. check our children
104     foreach my $role (@{$self->get_roles}) {
105         return 1 if $role->does_role($role_name);
106     }
107     return 0;
108 }
109
110 ## excluded roles
111
112 sub add_excluded_roles {
113     my ($self, @excluded_role_names) = @_;
114     $self->get_excluded_roles_map->{$_} = undef foreach @excluded_role_names;
115 }
116
117 sub get_excluded_roles_list {
118     my ($self) = @_;
119     keys %{$self->get_excluded_roles_map};
120 }
121
122 sub excludes_role {
123     my ($self, $role_name) = @_;
124     exists $self->get_excluded_roles_map->{$role_name} ? 1 : 0;
125 }
126
127 ## required methods
128
129 sub add_required_methods {
130     my ($self, @methods) = @_;
131     $self->get_required_methods_map->{$_} = undef foreach @methods;
132 }
133
134 sub remove_required_methods {
135     my ($self, @methods) = @_;
136     delete $self->get_required_methods_map->{$_} foreach @methods;
137 }
138
139 sub get_required_method_list {
140     my ($self) = @_;
141     keys %{$self->get_required_methods_map};
142 }
143
144 sub requires_method {
145     my ($self, $method_name) = @_;
146     exists $self->get_required_methods_map->{$method_name} ? 1 : 0;
147 }
148
149 sub _clean_up_required_methods {
150     my $self = shift;
151     foreach my $method ($self->get_required_method_list) {
152         $self->remove_required_methods($method)
153             if $self->has_method($method);
154     } 
155 }
156
157 ## methods
158
159 # NOTE:
160 # we delegate to some role_meta methods for convience here
161 # the Moose::Meta::Role is meant to be a read-only interface
162 # to the underlying role package, if you want to manipulate 
163 # that, just use ->role_meta
164
165 sub name    { (shift)->_role_meta->name    }
166 sub version { (shift)->_role_meta->version }
167
168 sub get_method      { (shift)->_role_meta->get_method(@_)   }
169 sub has_method      { (shift)->_role_meta->has_method(@_)   }
170 sub alias_method    { (shift)->_role_meta->alias_method(@_) }
171 sub get_method_list { 
172     my ($self) = @_;
173     grep { 
174         # NOTE:
175         # this is a kludge for now,... these functions 
176         # should not be showing up in the list at all, 
177         # but they do, so we need to switch Moose::Role
178         # and Moose to use Sub::Exporter to prevent this
179         !/^(meta|has|extends|blessed|confess|augment|inner|override|super|before|after|around|with|requires)$/ 
180     } $self->_role_meta->get_method_list;
181 }
182
183 # ... however the items in statis (attributes & method modifiers)
184 # can be removed and added to through this API
185
186 # attributes
187
188 sub add_attribute {
189     my ($self, $name, %attr_desc) = @_;
190     $self->get_attribute_map->{$name} = \%attr_desc;
191 }
192
193 sub has_attribute {
194     my ($self, $name) = @_;
195     exists $self->get_attribute_map->{$name} ? 1 : 0;
196 }
197
198 sub get_attribute {
199     my ($self, $name) = @_;
200     $self->get_attribute_map->{$name}
201 }
202
203 sub remove_attribute {
204     my ($self, $name) = @_;
205     delete $self->get_attribute_map->{$name}
206 }
207
208 sub get_attribute_list {
209     my ($self) = @_;
210     keys %{$self->get_attribute_map};
211 }
212
213 # method modifiers
214
215 # mimic the metaclass API
216 sub add_before_method_modifier { (shift)->_add_method_modifier('before', @_) }
217 sub add_around_method_modifier { (shift)->_add_method_modifier('around', @_) }
218 sub add_after_method_modifier  { (shift)->_add_method_modifier('after',  @_) }
219
220 sub _add_method_modifier {
221     my ($self, $modifier_type, $method_name, $method) = @_;
222     my $accessor = "get_${modifier_type}_method_modifiers_map";
223     $self->$accessor->{$method_name} = [] 
224         unless exists $self->$accessor->{$method_name};
225     push @{$self->$accessor->{$method_name}} => $method;
226 }
227
228 sub add_override_method_modifier {
229     my ($self, $method_name, $method) = @_;
230     $self->get_override_method_modifiers_map->{$method_name} = $method;    
231 }
232
233 sub has_before_method_modifiers { (shift)->_has_method_modifiers('before', @_) }
234 sub has_around_method_modifiers { (shift)->_has_method_modifiers('around', @_) }
235 sub has_after_method_modifiers  { (shift)->_has_method_modifiers('after',  @_) }
236
237 # override just checks for one,.. 
238 # but we can still re-use stuff
239 sub has_override_method_modifier { (shift)->_has_method_modifiers('override',  @_) }
240
241 sub _has_method_modifiers {
242     my ($self, $modifier_type, $method_name) = @_;
243     my $accessor = "get_${modifier_type}_method_modifiers_map";   
244     # NOTE:
245     # for now we assume that if it exists,.. 
246     # it has at least one modifier in it
247     (exists $self->$accessor->{$method_name}) ? 1 : 0;
248 }
249
250 sub get_before_method_modifiers { (shift)->_get_method_modifiers('before', @_) }
251 sub get_around_method_modifiers { (shift)->_get_method_modifiers('around', @_) }
252 sub get_after_method_modifiers  { (shift)->_get_method_modifiers('after',  @_) }
253
254 sub _get_method_modifiers {
255     my ($self, $modifier_type, $method_name) = @_;
256     my $accessor = "get_${modifier_type}_method_modifiers_map";
257     @{$self->$accessor->{$method_name}};
258 }
259
260 sub get_override_method_modifier {
261     my ($self, $method_name) = @_;
262     $self->get_override_method_modifiers_map->{$method_name};    
263 }
264
265 sub get_method_modifier_list {
266     my ($self, $modifier_type) = @_;
267     my $accessor = "get_${modifier_type}_method_modifiers_map";    
268     keys %{$self->$accessor};
269 }
270
271 ## applying a role to a class ...
272
273 sub apply {
274     my ($self, $other) = @_;
275     
276     if ($other->excludes_role($self->name)) {
277         confess "Conflict detected: " . $other->name . " excludes role '" . $self->name . "'";
278     }
279     
280 #    warn "... Checking " . $self->name . " for excluded methods";
281     foreach my $excluded_role_name ($self->get_excluded_roles_list) {
282 #        warn "... Checking if '$excluded_role_name' is done by " . $other->name . " for " . $self->name;
283         if ($other->does_role($excluded_role_name)) { 
284             confess "The class " . $other->name . " does the excluded role '$excluded_role_name'";
285         }
286         else {
287             if ($other->isa('Moose::Meta::Role')) {
288 #                warn ">>> The role " . $other->name . " does not do the excluded role '$excluded_role_name', so we are adding it in";
289                 $other->add_excluded_roles($excluded_role_name);
290             }
291             else {
292 #                warn ">>> The class " . $other->name . " does not do the excluded role '$excluded_role_name', so we can just go about our business";                
293             }
294         }
295     }    
296     
297     
298     # NOTE:
299     # we might need to move this down below the 
300     # the attributes so that we can require any 
301     # attribute accessors. However I am thinking 
302     # that maybe those are somehow exempt from 
303     # the require methods stuff.  
304     foreach my $required_method_name ($self->get_required_method_list) {
305         unless ($other->has_method($required_method_name)) {
306             if ($other->isa('Moose::Meta::Role')) {
307                 $other->add_required_methods($required_method_name);
308             }
309             else {
310                 confess "'" . $self->name . "' requires the method '$required_method_name' " . 
311                         "to be implemented by '" . $other->name . "'";
312             }
313         }
314         else {
315             # NOTE:
316             # we need to make sure that the method is 
317             # not a method modifier, because those do 
318             # not satisfy the requirements ...
319             my $method = $other->get_method($required_method_name);
320             # check if it is an override or a generated accessor ..
321             (!$method->isa('Moose::Meta::Method::Overriden') &&
322              !$method->isa('Class::MOP::Attribute::Accessor'))
323                 || confess "'" . $self->name . "' requires the method '$required_method_name' " . 
324                            "to be implemented by '" . $other->name . "', the method is only a method modifier";
325             # before/after/around methods are a little trickier
326             # since we wrap the original local method (if applicable)
327             # so we need to check if the original wrapped method is 
328             # from the same package, and not a wrap of the super method 
329             if ($method->isa('Class::MOP::Method::Wrapped')) {
330                 ($method->get_original_method->package_name eq $other->name)
331                     || confess "'" . $self->name . "' requires the method '$required_method_name' " . 
332                                "to be implemented by '" . $other->name . "', the method is only a method modifier";            
333             }
334         }
335     }       
336     
337     foreach my $attribute_name ($self->get_attribute_list) {
338         # it if it has one already
339         if ($other->has_attribute($attribute_name)) {
340             # see if we are being composed  
341             # into a role or not
342             if ($other->isa('Moose::Meta::Role')) {
343                 
344                 # FIXME:
345                 # it is possible for these attributes
346                 # to actually both be from the same 
347                 # origin (some common ancestor role)
348                 # so we need to find a way to check this
349                 
350                 # all attribute conflicts between roles 
351                 # result in an immediate fatal error 
352                 confess "Role '" . $self->name . "' has encountered an attribute conflict " . 
353                         "during composition. This is fatal error and cannot be disambiguated.";
354             }
355             else {
356                 # but if this is a class, we 
357                 # can safely skip adding the 
358                 # attribute to the class
359                 next;
360             }
361         }
362         else {
363             # add it, although it could be overriden 
364             $other->add_attribute(
365                 $attribute_name,
366                 %{$self->get_attribute($attribute_name)}
367             );
368         }
369     }
370     
371     foreach my $method_name ($self->get_method_list) {
372         # it if it has one already
373         if ($other->has_method($method_name)) {
374             # see if we are composing into a role
375             if ($other->isa('Moose::Meta::Role')) { 
376                 # method conflicts between roles result 
377                 # in the method becoming a requirement
378                 $other->add_required_methods($method_name);
379                 # NOTE:
380                 # we have to remove the method from our 
381                 # role, if this is being called from combine()
382                 # which means the meta is an anon class
383                 # this *may* cause problems later, but it 
384                 # is probably fairly safe to assume that 
385                 # anon classes will only be used internally
386                 # or by people who know what they are doing
387                 $other->_role_meta->remove_method($method_name)
388                     if $other->_role_meta->name =~ /__ANON__/;
389             }
390             else {
391                 next;
392             }
393         }
394         else {
395             # add it, although it could be overriden 
396             $other->alias_method(
397                 $method_name,
398                 $self->get_method($method_name)
399             );
400         }
401     }    
402     
403     foreach my $method_name ($self->get_method_modifier_list('override')) {
404         # it if it has one already then ...
405         if ($other->has_method($method_name)) {
406             # if it is being composed into another role
407             # we have a conflict here, because you cannot 
408             # combine an overriden method with a locally
409             # defined one 
410             if ($other->isa('Moose::Meta::Role')) { 
411                 confess "Role '" . $self->name . "' has encountered an 'override' method conflict " . 
412                         "during composition (A local method of the same name as been found). This " . 
413                         "is fatal error.";
414             }
415             else {
416                 # if it is a class, then we 
417                 # just ignore this here ...
418                 next;
419             }
420         }
421         else {
422             # if no local method is found, then we 
423             # must check if we are a role or class
424             if ($other->isa('Moose::Meta::Role')) { 
425                 # if we are a role, we need to make sure 
426                 # we dont have a conflict with the role 
427                 # we are composing into
428                 if ($other->has_override_method_modifier($method_name)) {
429                     confess "Role '" . $self->name . "' has encountered an 'override' method conflict " . 
430                             "during composition (Two 'override' methods of the same name encountered). " . 
431                             "This is fatal error.";
432                 }
433                 else {
434                     $other->add_override_method_modifier(
435                         $method_name,
436                         $self->get_override_method_modifier($method_name),
437                         $self->name
438                     );                    
439                 }
440             }
441             else {
442                 # if it is a class, we just add it
443                 $other->add_override_method_modifier(
444                     $method_name,
445                     $self->get_override_method_modifier($method_name),
446                     $self->name
447                 );
448             }
449         }
450     }    
451     
452     foreach my $method_name ($self->get_method_modifier_list('before')) {
453         $other->add_before_method_modifier(
454             $method_name,
455             $_
456         ) foreach $self->get_before_method_modifiers($method_name);
457     }    
458     
459     foreach my $method_name ($self->get_method_modifier_list('after')) {
460         $other->add_after_method_modifier(
461             $method_name,
462             $_
463         ) foreach $self->get_after_method_modifiers($method_name);
464     }    
465     
466     foreach my $method_name ($self->get_method_modifier_list('around')) {
467         $other->add_around_method_modifier(
468             $method_name,
469             $_
470         ) foreach $self->get_around_method_modifiers($method_name);
471     }    
472     
473     $other->add_role($self);
474 }
475
476 sub combine {
477     my ($class, @roles) = @_;
478     
479     my $combined = $class->new(
480         ':role_meta' => Moose::Meta::Class->create_anon_class()
481     );
482     
483     foreach my $role (@roles) {
484         $role->apply($combined);
485     }
486     
487     $combined->_clean_up_required_methods;   
488     
489     return $combined;
490 }
491
492 package Moose::Meta::Role::Method;
493
494 use strict;
495 use warnings;
496
497 our $VERSION = '0.01';
498
499 use base 'Class::MOP::Method';
500
501 1;
502
503 __END__
504
505 =pod
506
507 =head1 NAME
508
509 Moose::Meta::Role - The Moose Role metaclass
510
511 =head1 DESCRIPTION
512
513 Moose's Roles are being actively developed, please see L<Moose::Role> 
514 for more information. For the most part, this has no user-serviceable 
515 parts inside. It's API is still subject to some change (although 
516 probably not that much really).
517
518 =head1 METHODS
519
520 =over 4
521
522 =item B<meta>
523
524 =item B<new>
525
526 =item B<apply>
527
528 =item B<combine>
529
530 =back
531
532 =over 4
533
534 =item B<name>
535
536 =item B<version>
537
538 =item B<role_meta>
539
540 =back
541
542 =over 4
543
544 =item B<get_roles>
545
546 =item B<add_role>
547
548 =item B<does_role>
549
550 =back
551
552 =over 4
553
554 =item B<add_excluded_roles>
555
556 =item B<excludes_role>
557
558 =item B<get_excluded_roles_list>
559
560 =item B<get_excluded_roles_map>
561
562 =back
563
564 =over 4
565
566 =item B<get_method>
567
568 =item B<has_method>
569
570 =item B<alias_method>
571
572 =item B<get_method_list>
573
574 =back
575
576 =over 4
577
578 =item B<add_attribute>
579
580 =item B<has_attribute>
581
582 =item B<get_attribute>
583
584 =item B<get_attribute_list>
585
586 =item B<get_attribute_map>
587
588 =item B<remove_attribute>
589
590 =back
591
592 =over 4
593
594 =item B<add_required_methods>
595
596 =item B<remove_required_methods>
597
598 =item B<get_required_method_list>
599
600 =item B<get_required_methods_map>
601
602 =item B<requires_method>
603
604 =back
605
606 =over 4
607
608 =item B<add_after_method_modifier>
609
610 =item B<add_around_method_modifier>
611
612 =item B<add_before_method_modifier>
613
614 =item B<add_override_method_modifier>
615
616 =over 4
617
618 =back
619
620 =item B<has_after_method_modifiers>
621
622 =item B<has_around_method_modifiers>
623
624 =item B<has_before_method_modifiers>
625
626 =item B<has_override_method_modifier>
627
628 =over 4
629
630 =back
631
632 =item B<get_after_method_modifiers>
633
634 =item B<get_around_method_modifiers>
635
636 =item B<get_before_method_modifiers>
637
638 =item B<get_method_modifier_list>
639
640 =over 4
641
642 =back
643
644 =item B<get_override_method_modifier>
645
646 =item B<get_after_method_modifiers_map>
647
648 =item B<get_around_method_modifiers_map>
649
650 =item B<get_before_method_modifiers_map>
651
652 =item B<get_override_method_modifiers_map>
653
654 =back
655
656 =head1 BUGS
657
658 All complex software has bugs lurking in it, and this module is no 
659 exception. If you find a bug please either email me, or add the bug
660 to cpan-RT.
661
662 =head1 AUTHOR
663
664 Stevan Little E<lt>stevan@iinteractive.comE<gt>
665
666 =head1 COPYRIGHT AND LICENSE
667
668 Copyright 2006 by Infinity Interactive, Inc.
669
670 L<http://www.iinteractive.com>
671
672 This library is free software; you can redistribute it and/or modify
673 it under the same terms as Perl itself. 
674
675 =cut