0.18 ... pretty much ready to go
[gitmo/Moose.git] / lib / Moose / Meta / Role.pm
CommitLineData
e185c027 1
2package Moose::Meta::Role;
3
4use strict;
5use warnings;
6use metaclass;
7
bdabd620 8use Carp 'confess';
9use Scalar::Util 'blessed';
d30bc041 10use B 'svref_2object';
bdabd620 11
d44714be 12our $VERSION = '0.06';
13our $AUTHORITY = 'cpan:STEVAN';
e185c027 14
8ee73eeb 15use Moose::Meta::Class;
39b3bc94 16use Moose::Meta::Role::Method;
8ee73eeb 17
68efb014 18use base 'Class::MOP::Module';
80572233 19
68efb014 20## Attributes
80572233 21
22## roles
23
24__PACKAGE__->meta->add_attribute('roles' => (
25 reader => 'get_roles',
26 default => sub { [] }
e185c027 27));
28
d79e62fd 29## excluded roles
30
31__PACKAGE__->meta->add_attribute('excluded_roles_map' => (
32 reader => 'get_excluded_roles_map',
33 default => sub { {} }
34));
35
80572233 36## attributes
37
e185c027 38__PACKAGE__->meta->add_attribute('attribute_map' => (
39 reader => 'get_attribute_map',
40 default => sub { {} }
41));
42
1331430a 43## required methods
44
45__PACKAGE__->meta->add_attribute('required_methods' => (
46 reader => 'get_required_methods_map',
47 default => sub { {} }
48));
49
0558683c 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
bdabd620 72## Methods
80572233 73
68efb014 74sub method_metaclass { 'Moose::Meta::Role::Method' }
e185c027 75
80572233 76## subroles
77
78sub 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
b8aeb4dc 85sub calculate_all_roles {
86 my $self = shift;
87 my %seen;
88 grep { !$seen{$_->name}++ } $self, map { $_->calculate_all_roles } @{ $self->get_roles };
89}
90
80572233 91sub does_role {
92 my ($self, $role_name) = @_;
93 (defined $role_name)
94 || confess "You must supply a role name to look for";
bdabd620 95 # if we are it,.. then return true
96 return 1 if $role_name eq $self->name;
97 # otherwise.. check our children
80572233 98 foreach my $role (@{$self->get_roles}) {
bdabd620 99 return 1 if $role->does_role($role_name);
80572233 100 }
101 return 0;
102}
103
d79e62fd 104## excluded roles
105
106sub add_excluded_roles {
107 my ($self, @excluded_role_names) = @_;
108 $self->get_excluded_roles_map->{$_} = undef foreach @excluded_role_names;
109}
110
111sub get_excluded_roles_list {
112 my ($self) = @_;
113 keys %{$self->get_excluded_roles_map};
114}
115
116sub excludes_role {
117 my ($self, $role_name) = @_;
118 exists $self->get_excluded_roles_map->{$role_name} ? 1 : 0;
119}
120
1331430a 121## required methods
122
123sub add_required_methods {
124 my ($self, @methods) = @_;
125 $self->get_required_methods_map->{$_} = undef foreach @methods;
126}
127
38f1204c 128sub remove_required_methods {
129 my ($self, @methods) = @_;
130 delete $self->get_required_methods_map->{$_} foreach @methods;
131}
132
1331430a 133sub get_required_method_list {
134 my ($self) = @_;
135 keys %{$self->get_required_methods_map};
136}
137
138sub requires_method {
139 my ($self, $method_name) = @_;
140 exists $self->get_required_methods_map->{$method_name} ? 1 : 0;
141}
142
db1ab48d 143sub _clean_up_required_methods {
144 my $self = shift;
145 foreach my $method ($self->get_required_method_list) {
38f1204c 146 $self->remove_required_methods($method)
db1ab48d 147 if $self->has_method($method);
148 }
149}
150
80572233 151## methods
152
40e89659 153# FIXME:
093b12c2 154# this is an UGLY hack
155sub get_method_map {
156 my $self = shift;
5cf3dbcf 157 $self->{'%!methods'} ||= {};
093b12c2 158 $self->Moose::Meta::Class::get_method_map()
159}
160
161# FIXME:
40e89659 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
68efb014 166sub get_method { (shift)->Moose::Meta::Class::get_method(@_) }
68efb014 167sub has_method { (shift)->Moose::Meta::Class::has_method(@_) }
168sub alias_method { (shift)->Moose::Meta::Class::alias_method(@_) }
093b12c2 169sub get_method_list {
170 grep {
342ac837 171 !/^meta$/
093b12c2 172 } (shift)->Moose::Meta::Class::get_method_list(@_)
e185c027 173}
174
093b12c2 175sub find_method_by_name { (shift)->has_method(@_) }
176
e185c027 177# ... however the items in statis (attributes & method modifiers)
178# can be removed and added to through this API
179
180# attributes
181
182sub add_attribute {
a2eec5e7 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;
e185c027 193}
194
195sub has_attribute {
196 my ($self, $name) = @_;
197 exists $self->get_attribute_map->{$name} ? 1 : 0;
198}
199
200sub get_attribute {
201 my ($self, $name) = @_;
202 $self->get_attribute_map->{$name}
203}
204
205sub remove_attribute {
206 my ($self, $name) = @_;
207 delete $self->get_attribute_map->{$name}
208}
209
210sub get_attribute_list {
211 my ($self) = @_;
212 keys %{$self->get_attribute_map};
213}
214
0558683c 215# method modifiers
216
217# mimic the metaclass API
218sub add_before_method_modifier { (shift)->_add_method_modifier('before', @_) }
219sub add_around_method_modifier { (shift)->_add_method_modifier('around', @_) }
220sub add_after_method_modifier { (shift)->_add_method_modifier('after', @_) }
221
222sub _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
238sub 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
246sub has_before_method_modifiers { (shift)->_has_method_modifiers('before', @_) }
247sub has_around_method_modifiers { (shift)->_has_method_modifiers('around', @_) }
248sub has_after_method_modifiers { (shift)->_has_method_modifiers('after', @_) }
249
250# override just checks for one,..
251# but we can still re-use stuff
252sub has_override_method_modifier { (shift)->_has_method_modifiers('override', @_) }
253
254sub _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
263sub get_before_method_modifiers { (shift)->_get_method_modifiers('before', @_) }
264sub get_around_method_modifiers { (shift)->_get_method_modifiers('around', @_) }
265sub get_after_method_modifiers { (shift)->_get_method_modifiers('after', @_) }
266
267sub _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
273sub get_override_method_modifier {
274 my ($self, $method_name) = @_;
275 $self->get_override_method_modifiers_map->{$method_name};
276}
277
278sub get_method_modifier_list {
279 my ($self, $modifier_type) = @_;
280 my $accessor = "get_${modifier_type}_method_modifiers_map";
281 keys %{$self->$accessor};
282}
e185c027 283
bdabd620 284## applying a role to a class ...
285
a2eec5e7 286sub _check_excluded_roles {
bdabd620 287 my ($self, $other) = @_;
d79e62fd 288 if ($other->excludes_role($self->name)) {
289 confess "Conflict detected: " . $other->name . " excludes role '" . $self->name . "'";
290 }
d79e62fd 291 foreach my $excluded_role_name ($self->get_excluded_roles_list) {
9c429218 292 if ($other->does_role($excluded_role_name)) {
d79e62fd 293 confess "The class " . $other->name . " does the excluded role '$excluded_role_name'";
294 }
295 else {
296 if ($other->isa('Moose::Meta::Role')) {
d79e62fd 297 $other->add_excluded_roles($excluded_role_name);
298 }
a2eec5e7 299 # else -> ignore it :)
d79e62fd 300 }
301 }
a2eec5e7 302}
303
304sub _check_required_methods {
305 my ($self, $other) = @_;
1331430a 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) {
8c835eba 313
be4427d0 314 unless ($other->find_method_by_name($required_method_name)) {
fa1be058 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 }
0558683c 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') &&
39b3bc94 331 !$method->isa('Class::MOP::Method::Accessor'))
0558683c 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 }
a2eec5e7 344 }
345}
346
347sub _apply_attributes {
348 my ($self, $other) = @_;
bdabd620 349 foreach my $attribute_name ($self->get_attribute_list) {
db1ab48d 350 # it if it has one already
a2eec5e7 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)) {
db1ab48d 354 # see if we are being composed
355 # into a role or not
a2eec5e7 356 if ($other->isa('Moose::Meta::Role')) {
db1ab48d 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 {
db1ab48d 370 $other->add_attribute(
371 $attribute_name,
a2eec5e7 372 $self->get_attribute($attribute_name)
db1ab48d 373 );
374 }
a2eec5e7 375 }
376}
377
378sub _apply_methods {
379 my ($self, $other) = @_;
bdabd620 380 foreach my $method_name ($self->get_method_list) {
db1ab48d 381 # it if it has one already
d30bc041 382 if ($other->has_method($method_name) &&
383 # and if they are not the same thing ...
093b12c2 384 $other->get_method($method_name)->body != $self->get_method($method_name)->body) {
db1ab48d 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
68efb014 398 $other->Moose::Meta::Class::remove_method($method_name)
40e89659 399 if $other->name =~ /__COMPOSITE_ROLE_SANDBOX__/;
db1ab48d 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 }
a2eec5e7 412 }
413}
414
0558683c 415sub _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
471sub _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
483sub _apply_before_method_modifiers { (shift)->_apply_method_modifiers('before' => @_) }
484sub _apply_around_method_modifiers { (shift)->_apply_method_modifiers('around' => @_) }
485sub _apply_after_method_modifiers { (shift)->_apply_method_modifiers('after' => @_) }
486
b805c70c 487my $anon_counter = 0;
488
a2eec5e7 489sub apply {
490 my ($self, $other) = @_;
bdabd620 491
b805c70c 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 }
d7c04559 512
a2eec5e7 513 $self->_check_excluded_roles($other);
514 $self->_check_required_methods($other);
515
516 $self->_apply_attributes($other);
0558683c 517 $self->_apply_methods($other);
b805c70c 518
0558683c 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);
d63f8289 523
bdabd620 524 $other->add_role($self);
525}
526
db1ab48d 527sub combine {
528 my ($class, @roles) = @_;
529
40e89659 530 my $pkg_name = __PACKAGE__ . "::__COMPOSITE_ROLE_SANDBOX__::" . $anon_counter++;
68efb014 531 eval "package " . $pkg_name . "; our \$VERSION = '0.00';";
532 die $@ if $@;
533
534 my $combined = $class->initialize($pkg_name);
db1ab48d 535
536 foreach my $role (@roles) {
537 $role->apply($combined);
538 }
539
d05cd563 540 $combined->_clean_up_required_methods;
db1ab48d 541
542 return $combined;
543}
544
e185c027 5451;
546
547__END__
548
549=pod
550
551=head1 NAME
552
553Moose::Meta::Role - The Moose Role metaclass
554
555=head1 DESCRIPTION
556
d44714be 557Please see L<Moose::Role> for more information about roles.
558For the most part, this has no user-serviceable parts inside
559this module. It's API is still subject to some change (although
02a0fb52 560probably not that much really).
79592a54 561
e185c027 562=head1 METHODS
563
564=over 4
565
566=item B<meta>
567
568=item B<new>
569
78cd1d3b 570=item B<apply>
571
db1ab48d 572=item B<combine>
573
e185c027 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
80572233 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
d79e62fd 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
2b14ac61 606=item B<calculate_all_roles>
607
d79e62fd 608=back
609
610=over 4
611
68efb014 612=item B<method_metaclass>
613
be4427d0 614=item B<find_method_by_name>
615
e185c027 616=item B<get_method>
617
618=item B<has_method>
619
bdabd620 620=item B<alias_method>
621
e185c027 622=item B<get_method_list>
623
093b12c2 624=item B<get_method_map>
625
e185c027 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
1331430a 646=item B<add_required_methods>
647
38f1204c 648=item B<remove_required_methods>
649
1331430a 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
0558683c 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
e185c027 708=head1 BUGS
709
710All complex software has bugs lurking in it, and this module is no
711exception. If you find a bug please either email me, or add the bug
712to cpan-RT.
713
714=head1 AUTHOR
715
716Stevan Little E<lt>stevan@iinteractive.comE<gt>
717
718=head1 COPYRIGHT AND LICENSE
719
b77fdbed 720Copyright 2006, 2007 by Infinity Interactive, Inc.
e185c027 721
722L<http://www.iinteractive.com>
723
724This library is free software; you can redistribute it and/or modify
725it under the same terms as Perl itself.
726
b8aeb4dc 727=cut