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   438 use Carp ();
  24         26  
  24         527  
3 24     24   69 use Mouse::Util; # enables strict and warnings
  24         26  
  24         134  
4 24     24   91 use Mouse::Meta::Role;
  24         24  
  24         466  
5 24     24   3642 use Mouse::Meta::Role::Application;
  24         39  
  24         17838  
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 806 my $class = shift;
15 54         969 my $args = $class->Mouse::Object::BUILDARGS(@_);
16 54         816 my $roles = delete $args->{roles};
17 54         849 my $self = $class->create_anon_role(%{$args});
  54         1678  
18 54         789 foreach my $role_spec(@{$roles}) {
  54         1559  
19             my($role, $args) = ref($role_spec) eq 'ARRAY'
20 120 100       1705 ? @{$role_spec}
  115         3072  
21             : ($role_spec, {});
22 120         1585 $role->apply($self, %{$args});
  120         3284  
23             }
24 49         2354 return $self;
25             }
26              
27             sub get_method_list {
28 45     45 0 760 my($self) = @_;
29 128         2490 return grep { ! $self->{conflicting_methods}{$_} }
30 45         769 keys %{ $self->{methods} };
  45         1548  
31             }
32              
33             sub add_method {
34 163     163 0 931 my($self, $method_name, $code, $role) = @_;
35              
36 163 100 100     1361 if( ($self->{methods}{$method_name} || 0) == $code){
37             # This role already has the same method.
38 12         18 return;
39             }
40              
41 151 100       992 if($method_name eq 'meta'){
42 54         2162 $self->SUPER::add_method($method_name => $code);
43             }
44             else{
45             # no need to add a subroutine to the stash
46 97   100     311 my $roles = $self->{composed_roles_by_method}{$method_name} ||= [];
47 97         94 push @{$roles}, $role;
  97         126  
48 97 100       74 if(@{$roles} > 1){
  97         180  
49 7         19 $self->{conflicting_methods}{$method_name}++;
50             }
51 97         110 $self->{methods}{$method_name} = $code;
52             }
53 151         1724 return;
54             }
55              
56             sub get_method_body {
57 92     92 0 96 my($self, $method_name) = @_;
58 92         155 return $self->{methods}{$method_name};
59             }
60              
61             sub has_method {
62             # my($self, $method_name) = @_;
63 126     126 0 285 return 0; # to fool apply_methods() in combine()
64             }
65              
66             sub has_attribute {
67             # my($self, $method_name) = @_;
68 16     16 0 31 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 17 my $self = shift;
78 16         17 my $attr_name = shift;
79 16 50       29 my $spec = (@_ == 1 ? $_[0] : {@_});
80              
81 16         20 my $existing = $self->{attributes}{$attr_name};
82 16 100 100     50 if($existing && $existing != $spec){
83 2         12 $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         41 $self->SUPER::add_attribute($attr_name, $spec);
87 14         26 return;
88             }
89              
90             sub add_override_method_modifier {
91 5     5 0 7 my($self, $method_name, $code) = @_;
92              
93 5         10 my $existing = $self->{override_method_modifiers}{$method_name};
94 5 100 100     28 if($existing && $existing != $code){
95 1         5 $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         9 return;
101             }
102              
103             sub apply {
104 49     49 0 790 my $self = shift;
105 49         833 my $consumer = shift;
106              
107 49         934 Mouse::Meta::Role::Application::RoleSummation->new(@_)->apply($self, $consumer);
108 45         2488 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   813 my($self, $role, $consumer, @extra) = @_;
116              
117 49 100       850 if(exists $role->{conflicting_methods}){
118 7         17 my $consumer_class_name = $consumer->name;
119              
120 7         62 my @conflicting = grep{ !$consumer_class_name->can($_) }
121 7         7 keys %{ $role->{conflicting_methods} };
  7         16  
122              
123 7 100       19 if(@conflicting) {
124 4 100       9 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         40 grep{ !$seen{$_}++ } # uniq
131 10         17 map { $_->name }
132 5         5 map { @{$_} }
  5         12  
133 4         5 @{ $role->{composed_roles_by_method} }{@conflicting}
  4         25  
134             );
135              
136 4 100       17 $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       18 $consumer_class_name->can($_) && ! $consumer->has_method($_)
148 3         6 } keys %{ $role->{conflicting_methods} };
  3         5  
149 3 100       9 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         1 map { @{$_} }
  1         2  
159 1         1 @{ $role->{composed_roles_by_method} }{@changed_in_v2_0_0}
  1         2  
160             );
161              
162 1 50       5 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         1161 $self->SUPER::apply_methods($role, $consumer, @extra);
175 45         1532 return;
176             }
177              
178             package Mouse::Meta::Role::Composite;
179             1;
180             __END__