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