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