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