File Coverage

blib/lib/Mouse/Meta/Role/Composite.pm
Criterion Covered Total %
statement 94 95 98.9
branch 26 30 86.6
condition 10 10 100.0
subroutine 14 15 93.3
pod 0 10 0.0
total 144 160 90.0


line stmt bran cond sub pod time code
1             package Mouse::Meta::Role::Composite;
2 24     24   436 use Carp ();
  24         30  
  24         545  
3 24     24   79 use Mouse::Util; # enables strict and warnings
  24         28  
  24         148  
4 24     24   92 use Mouse::Meta::Role;
  24         29  
  24         437  
5 24     24   3643 use Mouse::Meta::Role::Application;
  24         31  
  24         18436  
6             our @ISA = qw(Mouse::Meta::Role);
7              
8             # FIXME: Mouse::Meta::Role::Composite does things in different way from Moose's
9             # Moose: creates a new class for the consumer, and applies roles to it.
10             # Mouse: creates a composite role and apply roles to the role,
11             # and then applies it to the consumer.
12              
13             sub new {
14 54     54 0 767 my $class = shift;
15 54         914 my $args = $class->Mouse::Object::BUILDARGS(@_);
16 54         823 my $roles = delete $args->{roles};
17 54         824 my $self = $class->create_anon_role(%{$args});
  54         1629  
18 54         760 foreach my $role_spec(@{$roles}) {
  54         1466  
19             my($role, $args) = ref($role_spec) eq 'ARRAY'
20 120 100       1634 ? @{$role_spec}
  115         2960  
21             : ($role_spec, {});
22 120         1525 $role->apply($self, %{$args});
  120         3141  
23             }
24 49         2265 return $self;
25             }
26              
27             sub get_method_list {
28 45     45 0 740 my($self) = @_;
29 128         2367 return grep { ! $self->{conflicting_methods}{$_} }
30 45         751 keys %{ $self->{methods} };
  45         1512  
31             }
32              
33             sub add_method {
34 163     163 0 875 my($self, $method_name, $code, $role) = @_;
35              
36 163 100 100     1373 if( ($self->{methods}{$method_name} || 0) == $code){
37             # This role already has the same method.
38 12         21 return;
39             }
40              
41 151 100       951 if($method_name eq 'meta'){
42 54         2117 $self->SUPER::add_method($method_name => $code);
43             }
44             else{
45             # no need to add a subroutine to the stash
46 97   100     323 my $roles = $self->{composed_roles_by_method}{$method_name} ||= [];
47 97         94 push @{$roles}, $role;
  97         141  
48 97 100       75 if(@{$roles} > 1){
  97         201  
49 7         17 $self->{conflicting_methods}{$method_name}++;
50             }
51 97         118 $self->{methods}{$method_name} = $code;
52             }
53 151         1682 return;
54             }
55              
56             sub get_method_body {
57 92     92 0 96 my($self, $method_name) = @_;
58 92         149 return $self->{methods}{$method_name};
59             }
60              
61             sub has_method {
62             # my($self, $method_name) = @_;
63 126     126 0 307 return 0; # to fool apply_methods() in combine()
64             }
65              
66             sub has_attribute {
67             # my($self, $method_name) = @_;
68 16     16 0 37 return 0; # to fool appply_attributes() in combine()
69             }
70              
71             sub has_override_method_modifier {
72             # my($self, $method_name) = @_;
73 0     0 0 0 return 0; # to fool apply_modifiers() in combine()
74             }
75              
76             sub add_attribute {
77 16     16 0 16 my $self = shift;
78 16         13 my $attr_name = shift;
79 16 50       31 my $spec = (@_ == 1 ? $_[0] : {@_});
80              
81 16         16 my $existing = $self->{attributes}{$attr_name};
82 16 100 100     53 if($existing && $existing != $spec){
83 2         8 $self->throw_error("We have encountered an attribute conflict with '$attr_name' "
84             . "during composition. This is fatal error and cannot be disambiguated.");
85             }
86 14         43 $self->SUPER::add_attribute($attr_name, $spec);
87 14         29 return;
88             }
89              
90             sub add_override_method_modifier {
91 5     5 0 6 my($self, $method_name, $code) = @_;
92              
93 5         7 my $existing = $self->{override_method_modifiers}{$method_name};
94 5 100 100     27 if($existing && $existing != $code){
95 1         4 $self->throw_error( "We have encountered an 'override' method conflict with '$method_name' during "
96             . "composition (Two 'override' methods of the same name encountered). "
97             . "This is fatal error.")
98             }
99 4         17 $self->SUPER::add_override_method_modifier($method_name, $code);
100 4         8 return;
101             }
102              
103             sub apply {
104 49     49 0 761 my $self = shift;
105 49         743 my $consumer = shift;
106              
107 49         870 Mouse::Meta::Role::Application::RoleSummation->new(@_)->apply($self, $consumer);
108 45         2319 return;
109             }
110              
111             package Mouse::Meta::Role::Application::RoleSummation;
112             our @ISA = qw(Mouse::Meta::Role::Application);
113              
114             sub apply_methods {
115 49     49   779 my($self, $role, $consumer, @extra) = @_;
116              
117 49 100       826 if(exists $role->{conflicting_methods}){
118 7         12 my $consumer_class_name = $consumer->name;
119              
120 7         74 my @conflicting = grep{ !$consumer_class_name->can($_) }
121 7         8 keys %{ $role->{conflicting_methods} };
  7         11  
122              
123 7 100       18 if(@conflicting) {
124 4 100       8 my $method_name_conflict = (@conflicting == 1
125             ? 'a method name conflict'
126             : 'method name conflicts');
127              
128 4         4 my %seen;
129             my $roles = Mouse::Util::quoted_english_list(
130 10         31 grep{ !$seen{$_}++ } # uniq
131 10         20 map { $_->name }
132 5         4 map { @{$_} }
  5         13  
133 4         4 @{ $role->{composed_roles_by_method} }{@conflicting}
  4         7  
134             );
135              
136 4 100       13 $self->throw_error(sprintf
137             q{Due to %s in roles %s,}
138             . q{ the method%s %s must be implemented or excluded by '%s'},
139             $method_name_conflict,
140             $roles,
141             (@conflicting > 1 ? 's' : ''),
142             Mouse::Util::quoted_english_list(@conflicting),
143             $consumer_class_name);
144             }
145              
146             my @changed_in_v2_0_0 = grep {
147 2 50       12 $consumer_class_name->can($_) && ! $consumer->has_method($_)
148 3         3 } keys %{ $role->{conflicting_methods} };
  3         6  
149 3 100       10 if (@changed_in_v2_0_0) {
150 1 50       2 my $method_name_conflict = (@changed_in_v2_0_0 == 1
151             ? 'a method name conflict'
152             : 'method name conflicts');
153              
154 1         1 my %seen;
155             my $roles = Mouse::Util::quoted_english_list(
156 2         5 grep{ !$seen{$_}++ } # uniq
157 2         4 map { $_->name }
158 1         0 map { @{$_} }
  1         2  
159 1         1 @{ $role->{composed_roles_by_method} }{@changed_in_v2_0_0}
  1         3  
160             );
161              
162 1 50       3 Carp::cluck(sprintf
163             q{Due to %s in roles %s,}
164             . q{ the behavior of method%s %s might be incompatible with Moose}
165             . q{, check out %s},
166             $method_name_conflict,
167             $roles,
168             (@changed_in_v2_0_0 > 1 ? 's' : ''),
169             Mouse::Util::quoted_english_list(@changed_in_v2_0_0),
170             $consumer_class_name);
171             }
172             }
173              
174 45         1158 $self->SUPER::apply_methods($role, $consumer, @extra);
175 45         1572 return;
176             }
177              
178             package Mouse::Meta::Role::Composite;
179             1;
180             __END__