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