Beginning of dzilization
[gitmo/Moose.git] / lib / Moose / Meta / Role / Application / RoleSummation.pm
CommitLineData
fb1e11d5 1package Moose::Meta::Role::Application::RoleSummation;
2
3use strict;
4use warnings;
5use metaclass;
6
d2682e6e 7use Scalar::Util 'blessed';
fb1e11d5 8
9use Moose::Meta::Role::Composite;
10
fb1e11d5 11our $AUTHORITY = 'cpan:STEVAN';
12
13use base 'Moose::Meta::Role::Application';
14
28412c0b 15__PACKAGE__->meta->add_attribute('role_params' => (
16 reader => 'role_params',
17 default => sub { {} }
18));
19
20sub get_exclusions_for_role {
21 my ($self, $role) = @_;
22 $role = $role->name if blessed $role;
c8b8d92f 23 my $excludes_key = exists $self->role_params->{$role}->{'-excludes'} ?
24 '-excludes' : 'excludes';
25 if ($self->role_params->{$role} && defined $self->role_params->{$role}->{$excludes_key}) {
26 if (ref $self->role_params->{$role}->{$excludes_key} eq 'ARRAY') {
27 return $self->role_params->{$role}->{$excludes_key};
28412c0b 28 }
c8b8d92f 29 return [ $self->role_params->{$role}->{$excludes_key} ];
28412c0b 30 }
31 return [];
32}
33
34sub get_method_aliases_for_role {
35 my ($self, $role) = @_;
36 $role = $role->name if blessed $role;
c8b8d92f 37 my $alias_key = exists $self->role_params->{$role}->{'-alias'} ?
38 '-alias' : 'alias';
39 if ($self->role_params->{$role} && defined $self->role_params->{$role}->{$alias_key}) {
40 return $self->role_params->{$role}->{$alias_key};
28412c0b 41 }
d03bd989 42 return {};
28412c0b 43}
44
45sub is_method_excluded {
46 my ($self, $role, $method_name) = @_;
47 foreach ($self->get_exclusions_for_role($role->name)) {
48 return 1 if $_ eq $method_name;
49 }
50 return 0;
51}
52
53sub is_method_aliased {
54 my ($self, $role, $method_name) = @_;
55 exists $self->get_method_aliases_for_role($role->name)->{$method_name} ? 1 : 0
56}
57
58sub is_aliased_method {
59 my ($self, $role, $method_name) = @_;
d03bd989 60 my %aliased_names = reverse %{$self->get_method_aliases_for_role($role->name)};
28412c0b 61 exists $aliased_names{$method_name} ? 1 : 0;
62}
63
fb1e11d5 64sub check_role_exclusions {
65 my ($self, $c) = @_;
66
88cfb4cb 67 my %excluded_roles;
68 for my $role (@{ $c->get_roles }) {
69 my $name = $role->name;
70
71 for my $excluded ($role->get_excluded_roles_list) {
72 push @{ $excluded_roles{$excluded} }, $name;
73 }
74 }
fb1e11d5 75
76 foreach my $role (@{$c->get_roles}) {
88cfb4cb 77 foreach my $excluded (keys %excluded_roles) {
78 next unless $role->does_role($excluded);
79
80 my @excluding = @{ $excluded_roles{$excluded} };
70ea9161 81
82 require Moose;
5b5b7c12 83 Moose->throw_error(sprintf "Conflict detected: Role%s %s exclude%s role '%s'", (@excluding == 1 ? '' : 's'), join(', ', @excluding), (@excluding == 1 ? 's' : ''), $excluded);
fb1e11d5 84 }
85 }
86
88cfb4cb 87 $c->add_excluded_roles(keys %excluded_roles);
fb1e11d5 88}
89
90sub check_required_methods {
91 my ($self, $c) = @_;
92
d2682e6e 93 my %all_required_methods =
7e79d987 94 map { $_->name => $_ }
d2682e6e 95 map { $_->get_required_method_list }
96 @{$c->get_roles};
fb1e11d5 97
98 foreach my $role (@{$c->get_roles}) {
99 foreach my $required (keys %all_required_methods) {
d03bd989 100
fb1e11d5 101 delete $all_required_methods{$required}
28412c0b 102 if $role->has_method($required)
103 || $self->is_aliased_method($role, $required);
fb1e11d5 104 }
105 }
106
7e79d987 107 $c->add_required_methods(values %all_required_methods);
fb1e11d5 108}
109
709c321c 110sub check_required_attributes {
d03bd989 111
709c321c 112}
113
fb1e11d5 114sub apply_attributes {
115 my ($self, $c) = @_;
d03bd989 116
f785aad8 117 my @all_attributes;
118
119 for my $role ( @{ $c->get_roles } ) {
120 push @all_attributes,
121 map { $role->get_attribute($_) } $role->get_attribute_list;
122 }
d03bd989 123
fb1e11d5 124 my %seen;
125 foreach my $attr (@all_attributes) {
f785aad8 126 my $name = $attr->name;
127
128 if ( exists $seen{$name} ) {
129 next if $seen{$name}->is_same_as($attr);
130
131 my $role1 = $seen{$name}->associated_role->name;
132 my $role2 = $attr->associated_role->name;
133
134 require Moose;
135 Moose->throw_error(
136 "We have encountered an attribute conflict with '$name' "
137 . "during role composition. "
138 . " This attribute is defined in both $role1 and $role2."
139 . " This is fatal error and cannot be disambiguated." );
fb1e11d5 140 }
f785aad8 141
142 $seen{$name} = $attr;
fb1e11d5 143 }
144
d03bd989 145 foreach my $attr (@all_attributes) {
f785aad8 146 $c->add_attribute( $attr->clone );
fb1e11d5 147 }
148}
149
150sub apply_methods {
151 my ($self, $c) = @_;
d03bd989 152
fb1e11d5 153 my @all_methods = map {
28412c0b 154 my $role = $_;
155 my $aliases = $self->get_method_aliases_for_role($role);
156 my %excludes = map { $_ => undef } @{ $self->get_exclusions_for_role($role) };
157 (
d03bd989 158 (map {
28412c0b 159 exists $excludes{$_} ? () :
d03bd989 160 +{
28412c0b 161 role => $role,
162 name => $_,
163 method => $role->get_method($_),
164 }
ba7d613d 165 } map { $_->name }
166 grep { !$_->isa('Class::MOP::Method::Meta') }
167 $role->_get_local_methods),
d03bd989 168 (map {
169 +{
28412c0b 170 role => $role,
171 name => $aliases->{$_},
172 method => $role->get_method($_),
d03bd989 173 }
28412c0b 174 } keys %$aliases)
175 );
fb1e11d5 176 } @{$c->get_roles};
d03bd989 177
fb1e11d5 178 my (%seen, %method_map);
179 foreach my $method (@all_methods) {
49b8c93a 180 my $seen = $seen{$method->{name}};
181
182 if ($seen) {
183 if ($seen->{method}->body != $method->{method}->body) {
bb153262 184 $c->add_conflicting_method(
49b8c93a 185 name => $method->{name},
78485053 186 roles => [$method->{role}->name, $seen->{role}->name],
49b8c93a 187 );
188
fb1e11d5 189 delete $method_map{$method->{name}};
190 next;
d03bd989 191 }
192 }
193
49b8c93a 194 $seen{$method->{name}} = $method;
fb1e11d5 195 $method_map{$method->{name}} = $method->{method};
196 }
197
87e63626 198 $c->add_method($_ => $method_map{$_}) for keys %method_map;
fb1e11d5 199}
200
201sub apply_override_method_modifiers {
202 my ($self, $c) = @_;
d03bd989 203
fb1e11d5 204 my @all_overrides = map {
205 my $role = $_;
d03bd989 206 map {
207 +{
fb1e11d5 208 name => $_,
209 method => $role->get_override_method_modifier($_),
210 }
211 } $role->get_method_modifier_list('override');
212 } @{$c->get_roles};
d03bd989 213
fb1e11d5 214 my %seen;
215 foreach my $override (@all_overrides) {
70ea9161 216 if ( $c->has_method($override->{name}) ){
217 require Moose;
218 Moose->throw_error( "Role '" . $c->name . "' has encountered an 'override' method conflict " .
219 "during composition (A local method of the same name as been found). This " .
220 "is fatal error." )
221 }
fb1e11d5 222 if (exists $seen{$override->{name}}) {
70ea9161 223 if ( $seen{$override->{name}} != $override->{method} ) {
224 require Moose;
225 Moose->throw_error( "We have encountered an 'override' method conflict during " .
226 "composition (Two 'override' methods of the same name encountered). " .
227 "This is fatal error.")
228 }
fb1e11d5 229 }
230 $seen{$override->{name}} = $override->{method};
231 }
d03bd989 232
fb1e11d5 233 $c->add_override_method_modifier(
234 $_->{name}, $_->{method}
235 ) for @all_overrides;
d03bd989 236
fb1e11d5 237}
238
239sub apply_method_modifiers {
240 my ($self, $modifier_type, $c) = @_;
241 my $add = "add_${modifier_type}_method_modifier";
242 my $get = "get_${modifier_type}_method_modifiers";
243 foreach my $role (@{$c->get_roles}) {
244 foreach my $method_name ($role->get_method_modifier_list($modifier_type)) {
245 $c->$add(
246 $method_name,
247 $_
248 ) foreach $role->$get($method_name);
249 }
250 }
251}
252
fb1e11d5 2531;
254
ad46f524 255# ABSTRACT: Combine two or more roles
256
fb1e11d5 257__END__
258
259=pod
260
fb1e11d5 261=head1 DESCRIPTION
262
d03bd989 263Summation composes two traits, forming the union of non-conflicting
fb1e11d5 264bindings and 'disabling' the conflicting bindings
265
266=head2 METHODS
267
268=over 4
269
270=item B<new>
271
272=item B<meta>
273
28412c0b 274=item B<role_params>
275
276=item B<get_exclusions_for_role>
277
278=item B<get_method_aliases_for_role>
279
280=item B<is_aliased_method>
281
282=item B<is_method_aliased>
283
284=item B<is_method_excluded>
285
fb1e11d5 286=item B<apply>
287
709c321c 288=item B<check_role_exclusions>
289
fb1e11d5 290=item B<check_required_methods>
291
709c321c 292=item B<check_required_attributes>
fb1e11d5 293
294=item B<apply_attributes>
295
296=item B<apply_methods>
297
298=item B<apply_method_modifiers>
299
fb1e11d5 300=item B<apply_override_method_modifiers>
301
302=back
303
304=head1 BUGS
305
d4048ef3 306See L<Moose/BUGS> for details on reporting bugs.
fb1e11d5 307
fb1e11d5 308=cut
309