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