Refactoring
[gitmo/Mouse.git] / lib / Mouse / Meta / Role.pm
CommitLineData
a2227e71 1package Mouse::Meta::Role;
2use strict;
3use warnings;
59089ec3 4use Carp 'confess';
74be9f76 5
3a63a2e7 6use base qw(Mouse::Meta::Module);
a2227e71 7
acf0f643 8do {
9 my %METACLASS_CACHE;
10
11 # because Mouse doesn't introspect existing classes, we're forced to
12 # only pay attention to other Mouse classes
13 sub _metaclass_cache {
14 my $class = shift;
15 my $name = shift;
16 return $METACLASS_CACHE{$name};
17 }
18
19 sub initialize {
3a63a2e7 20 my($class, $package_name, @args) = @_;
21
22 ($package_name && !ref($package_name))\r
23 || confess("You must pass a package name and it cannot be blessed");\r
24
25 return $METACLASS_CACHE{$package_name}
26 ||= $class->_new(package => $package_name, @args);
acf0f643 27 }
28};
29
3a63a2e7 30sub _new {
acf0f643 31 my $class = shift;
32 my %args = @_;
33
3a63a2e7 34 $args{methods} ||= {};
59089ec3 35 $args{attributes} ||= {};
36 $args{required_methods} ||= [];
47f36c05 37 $args{roles} ||= [];
274b6cce 38
acf0f643 39 bless \%args, $class;
40}
a2227e71 41
59089ec3 42sub add_required_methods {
43 my $self = shift;
44 my @methods = @_;
45 push @{$self->{required_methods}}, @methods;
46}
47
274b6cce 48sub add_attribute {
49 my $self = shift;
50 my $name = shift;
69ac1dcf 51 my $spec = shift;
9c85e9dc 52 $self->{attributes}->{$name} = $spec;
da0c885d 53}
54
274b6cce 55sub has_attribute { exists $_[0]->{attributes}->{$_[1]} }
56sub get_attribute_list { keys %{ $_[0]->{attributes} } }
69ac1dcf 57sub get_attribute { $_[0]->{attributes}->{$_[1]} }
274b6cce 58
3a63a2e7 59sub _check_required_methods{
60 my($role, $class, $args, @other_roles) = @_;
61
62 if($class->isa('Mouse::Meta::Class')){
63 my $class_name = $class->name;
64 foreach my $method_name(@{$role->{required_methods}}){
65 unless($class_name->can($method_name)){
66 my $role_name = $role->name;
67 my $has_method = 0;
68
69 foreach my $another_role_spec(@other_roles){
70 my $another_role_name = $another_role_spec->[0];
71 if($role_name ne $another_role_name && $another_role_name->can($method_name)){
72 $has_method = 1;
73 last;
74 }
75 }
76
77 confess "'$role_name' requires the method '$method_name' to be implemented by '$class_name'"
78 unless $has_method;
79 }
80 }
81 }
2e92bb89 82
3a63a2e7 83 return;
2e92bb89 84}
85
3a63a2e7 86sub _apply_methods{
87 my($role, $class, $args) = @_;
da0c885d 88
3a63a2e7 89 my $role_name = $role->name;
90 my $class_name = $class->name;
91 my $alias = $args->{alias};
e0b163e1 92
3a63a2e7 93 foreach my $method_name($role->get_method_list){
94 next if $method_name eq 'meta';
95
96 my $code = $role_name->can($method_name);
97 if(do{ no strict 'refs'; defined &{$class_name . '::' . $method_name} }){
98 # XXX what's Moose's behavior?
99 }
100 else{
101 $class->add_method($method_name => $code);
2e92bb89 102 }
2e92bb89 103
3a63a2e7 104 if($alias && $alias->{$method_name}){
105 my $dstname = $alias->{$method_name};
106 if(do{ no strict 'refs'; defined &{$class_name . '::' . $dstname} }){
107 # XXX wat's Moose's behavior?
21498b08 108 }
3a63a2e7 109 else{
110 $class->add_method($dstname => $code);
2e92bb89 111 }
59089ec3 112 }
113 }
114
3a63a2e7 115 return;
116}
117
118sub _apply_attributes{
119 my($role, $class, $args) = @_;
120
b1b81553 121 if ($class->isa('Mouse::Meta::Class')) {
122 # apply role to class
3a63a2e7 123 for my $attr_name ($role->get_attribute_list) {
124 next if $class->has_attribute($attr_name);
125
126 my $spec = $role->get_attribute($attr_name);
05b9dc92 127
3a63a2e7 128 my $attr_metaclass = 'Mouse::Meta::Attribute';
05b9dc92 129 if ( my $metaclass_name = $spec->{metaclass} ) {
3a63a2e7 130 $attr_metaclass = Mouse::Util::resolve_metaclass_alias(
05b9dc92 131 'Attribute',
132 $metaclass_name
133 );
05b9dc92 134 }
135
3a63a2e7 136 $attr_metaclass->create($class, $attr_name => %$spec);
b1b81553 137 }
138 } else {
139 # apply role to role
3a63a2e7 140 for my $attr_name ($role->get_attribute_list) {
141 next if $class->has_attribute($attr_name);
142
143 my $spec = $role->get_attribute($attr_name);
144 $class->add_attribute($attr_name => $spec);
b1b81553 145 }
da0c885d 146 }
d99db7b6 147
3a63a2e7 148 return;
149}
150
151sub _apply_modifiers{
152 my($role, $class, $args) = @_;
153
67199842 154 for my $modifier_type (qw/before after around override/) {
3a63a2e7 155 my $add_modifier = "add_${modifier_type}_method_modifier";
156 my $modifiers = $role->{"${modifier_type}_method_modifiers"};
d99db7b6 157
3a63a2e7 158 while(my($method_name, $modifier_codes) = each %{$modifiers}){
159 foreach my $code(@{$modifier_codes}){
160 $class->$add_modifier($method_name => $code);
d99db7b6 161 }
162 }
163 }
3a63a2e7 164 return;
da0c885d 165}
0fc8adbc 166
3a63a2e7 167sub _append_roles{
168 my($role, $class, $args) = @_;
21498b08 169
3a63a2e7 170 my $roles = $class->isa('Mouse::Meta::Class') ? $class->roles : $class->get_roles;
171
172 foreach my $r($role, @{$role->get_roles}){
173 if(!$class->does_role($r->name)){
174 push @{$roles}, $r;
21498b08 175 }
176 }
3a63a2e7 177 return;
178}
21498b08 179
3a63a2e7 180# Moose uses Application::ToInstance, Application::ToClass, Application::ToRole
181sub apply {
182 my($self, $class, %args) = @_;
183
184 if ($class->isa('Mouse::Object')) {
185 Carp::croak('Mouse does not support Application::ToInstance yet');
21498b08 186 }
187
3a63a2e7 188 $self->_check_required_methods($class, \%args);
189 $self->_apply_methods($class, \%args);
190 $self->_apply_attributes($class, \%args);
191 $self->_apply_modifiers($class, \%args);
192 $self->_append_roles($class, \%args);
193 return;
194}
21498b08 195
3a63a2e7 196sub combine_apply {
197 my(undef, $class, @roles) = @_;
05b9dc92 198
3a63a2e7 199 foreach my $role_spec (@roles) {
200 my($role_name, $args) = @{$role_spec};
21498b08 201
3a63a2e7 202 my $role = $role_name->meta;
21498b08 203
3a63a2e7 204 $role->_check_required_methods($class, $args, @roles);
205 $role->_apply_methods($class, $args);
206 $role->_apply_attributes($class, $args);
207 $role->_apply_modifiers($class, $args);
208 $role->_append_roles($class, $args);
21498b08 209 }
3a63a2e7 210 return;
21498b08 211}
212
67199842 213for my $modifier_type (qw/before after around override/) {
3a63a2e7 214
215 my $modifier = "${modifier_type}_method_modifiers";
216 my $add_method_modifier = sub {
fc0e0bbd 217 my ($self, $method_name, $method) = @_;
218
3a63a2e7 219 push @{ $self->{$modifier}->{$method_name} ||= [] }, $method;
220 return;
fc0e0bbd 221 };
3a63a2e7 222 my $get_method_modifiers = sub {
223 my ($self, $method_name) = @_;
224 return @{ $self->{$modifier}->{$method_name} ||= [] }
c2f128e7 225 };
c2f128e7 226
3a63a2e7 227 no strict 'refs';
228 *{ 'add_' . $modifier_type . '_method_modifier' } = $add_method_modifier;
229 *{ 'get_' . $modifier_type . '_method_modifiers' } = $get_method_modifiers;
230}
47f36c05 231
3a63a2e7 232sub get_roles { $_[0]->{roles} }
67199842 233
234# This is currently not passing all the Moose tests.
235sub does_role {
236 my ($self, $role_name) = @_;
237
238 (defined $role_name)
239 || confess "You must supply a role name to look for";
240
241 # if we are it,.. then return true
242 return 1 if $role_name eq $self->name;
3a63a2e7 243 # otherwise.. check our children
244 for my $role (@{ $self->get_roles }) {
67199842 245 return 1 if $role->does_role($role_name);
246 }
247 return 0;
248}
249
250
a2227e71 2511;
252