move this back to HasMethods, since moose roles will need it too
[gitmo/Class-MOP.git] / lib / Class / MOP / Mixin / HasMethods.pm
1 package Class::MOP::Mixin::HasMethods;
2
3 use strict;
4 use warnings;
5
6 use Class::MOP::Method::Meta;
7
8 our $VERSION   = '1.09';
9 $VERSION = eval $VERSION;
10 our $AUTHORITY = 'cpan:STEVAN';
11
12 use Scalar::Util 'blessed';
13 use Carp         'confess';
14 use Sub::Name    'subname';
15
16 use base 'Class::MOP::Mixin';
17
18 sub method_metaclass         { $_[0]->{'method_metaclass'}            }
19 sub wrapped_method_metaclass { $_[0]->{'wrapped_method_metaclass'}    }
20 sub _meta_method_class       { 'Class::MOP::Method::Meta'             }
21
22 sub _add_meta_method {
23     my $self = shift;
24     my $existing_method = $self->can('find_method_by_name')
25                               ? $self->find_method_by_name('meta')
26                               : $self->get_method('meta');
27     return if $existing_method
28            && $existing_method->isa($self->_meta_method_class);
29     $self->add_method(
30         'meta' => $self->_meta_method_class->wrap(
31             name                 => 'meta',
32             package_name         => $self->name,
33             associated_metaclass => $self,
34         )
35     );
36 }
37
38 # This doesn't always get initialized in a constructor because there is a
39 # weird object construction path for subclasses of Class::MOP::Class. At one
40 # point, this always got initialized by calling into the XS code first, but
41 # that is no longer guaranteed to happen.
42 sub _method_map { $_[0]->{'methods'} ||= {} }
43
44 sub wrap_method_body {
45     my ( $self, %args ) = @_;
46
47     ( 'CODE' eq ref $args{body} )
48         || confess "Your code block must be a CODE reference";
49
50     $self->method_metaclass->wrap(
51         package_name => $self->name,
52         %args,
53     );
54 }
55
56 sub add_method {
57     my ( $self, $method_name, $method ) = @_;
58     ( defined $method_name && length $method_name )
59         || confess "You must define a method name";
60
61     my $package_name = $self->name;
62
63     my $body;
64     if ( blessed($method) ) {
65         $body = $method->body;
66         if ( $method->package_name ne $package_name ) {
67             $method = $method->clone(
68                 package_name => $package_name,
69                 name         => $method_name,
70             ) if $method->can('clone');
71         }
72
73         $method->attach_to_class($self);
74     }
75     else {
76         # If a raw code reference is supplied, its method object is not created.
77         # The method object won't be created until required.
78         $body = $method;
79     }
80
81     $self->_method_map->{$method_name} = $method;
82
83     my ( $current_package, $current_name ) = Class::MOP::get_code_info($body);
84
85     if ( !defined $current_name || $current_name =~ /^__ANON__/ ) {
86         my $full_method_name = ( $package_name . '::' . $method_name );
87         subname( $full_method_name => $body );
88     }
89
90     $self->add_package_symbol(
91         { sigil => '&', type => 'CODE', name => $method_name },
92         $body,
93     );
94 }
95
96 sub _code_is_mine {
97     my ( $self, $code ) = @_;
98
99     my ( $code_package, $code_name ) = Class::MOP::get_code_info($code);
100
101     return $code_package && $code_package eq $self->name
102         || ( $code_package eq 'constant' && $code_name eq '__ANON__' );
103 }
104
105 sub has_method {
106     my ( $self, $method_name ) = @_;
107
108     ( defined $method_name && length $method_name )
109         || confess "You must define a method name";
110
111     return defined( $self->_get_maybe_raw_method($method_name) );
112 }
113
114 sub get_method {
115     my ( $self, $method_name ) = @_;
116
117     ( defined $method_name && length $method_name )
118         || confess "You must define a method name";
119
120     my $method = $self->_get_maybe_raw_method($method_name)
121         or return;
122
123     return $method if blessed $method;
124
125     return $self->_method_map->{$method_name} = $self->wrap_method_body(
126         body                 => $method,
127         name                 => $method_name,
128         associated_metaclass => $self,
129     );
130 }
131
132 sub _get_maybe_raw_method {
133     my ( $self, $method_name ) = @_;
134
135     my $method_map = $self->_method_map;
136     my $map_entry  = $method_map->{$method_name};
137     my $code       = $self->get_package_symbol(
138         {
139             name  => $method_name,
140             sigil => '&',
141             type  => 'CODE',
142         }
143     );
144
145     # The !$code case seems to happen in some weird cases where methods
146     # modifiers are added via roles or some other such bizareness. Honestly, I
147     # don't totally understand this, but returning the entry works, and keeps
148     # various MX modules from blowing up. - DR
149     return $map_entry
150         if blessed $map_entry && ( !$code || $map_entry->body == $code );
151
152     unless ($map_entry) {
153         return unless $code && $self->_code_is_mine($code);
154     }
155
156     return $code;
157 }
158
159 sub remove_method {
160     my ( $self, $method_name ) = @_;
161     ( defined $method_name && length $method_name )
162         || confess "You must define a method name";
163
164     my $removed_method = delete $self->_full_method_map->{$method_name};
165
166     $self->remove_package_symbol(
167         { sigil => '&', type => 'CODE', name => $method_name } );
168
169     $removed_method->detach_from_class
170         if $removed_method && blessed $removed_method;
171
172     # still valid, since we just removed the method from the map
173     $self->update_package_cache_flag;
174
175     return $removed_method;
176 }
177
178 sub get_method_list {
179     my $self = shift;
180
181     my $namespace = $self->namespace;
182
183     # Constants may show up as some sort of non-GLOB reference in the
184     # namespace hash ref, depending on the Perl version.
185     return grep {
186         defined $namespace->{$_}
187             && ( ref( \$namespace->{$_} ) ne 'GLOB'
188             || *{ $namespace->{$_} }{CODE} )
189             && $self->has_method($_)
190         }
191         keys %{$namespace};
192 }
193
194 # This should probably be what get_method_list actually does, instead of just
195 # returning names. This was created as a much faster alternative to
196 # $meta->get_method($_) for $meta->get_method_list
197 sub _get_local_methods {
198     my $self = shift;
199
200     my $namespace = $self->namespace;
201
202     return map { $self->get_method($_) }
203         grep {
204         defined $namespace->{$_}
205             && ( ref $namespace->{$_}
206             || *{ $namespace->{$_} }{CODE} )
207         }
208         keys %{$namespace};
209 }
210
211 sub _restore_metamethods_from {
212     my $self = shift;
213     my ($old_meta) = @_;
214
215     for my $method ($old_meta->_get_local_methods) {
216         # XXX: this is pretty gross. the issue here is that
217         # CMOP::Method::Wrapped objects are subclasses of CMOP::Method, but
218         # when we get to moose, they'll need to be compatible with
219         # Moose::Meta::Method, which isn't possible. the right solution here is
220         # to make ::Wrapped into a role that gets applied to whatever the
221         # method_metaclass happens to be and get rid of
222         # wrapped_method_metaclass entirely, but that's not going to happen
223         # until we ditch cmop and get roles into the bootstrapping, so.
224         # i'm not maintaining the previous behavior of turning them into
225         # instances of the new method_metaclass because that's equally broken,
226         # and at least this way any issues will at least be detectable and
227         # potentially fixable. -doy
228         if (!$method->isa($self->wrapped_method_metaclass)) {
229             $method->_make_compatible_with($self->method_metaclass);
230         }
231         $self->add_method($method->name => $method);
232     }
233 }
234
235 1;
236
237 __END__
238
239 =pod
240
241 =head1 NAME
242
243 Class::MOP::Mixin::HasMethods - Methods for metaclasses which have methods
244
245 =head1 DESCRIPTION
246
247 This class implements methods for metaclasses which have methods
248 (L<Class::MOP::Package> and L<Moose::Meta::Role>). See L<Class::MOP::Package>
249 for API details.
250
251 =head1 AUTHORS
252
253 Dave Rolsky E<lt>autarch@urth.orgE<gt>
254
255 =head1 COPYRIGHT AND LICENSE
256
257 Copyright 2006-2010 by Infinity Interactive, Inc.
258
259 L<http://www.iinteractive.com>
260
261 This library is free software; you can redistribute it and/or modify
262 it under the same terms as Perl itself.
263
264 =cut