5dced15c3e929a039acc67620e6d937cb67cabf8
[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
15 use base 'Class::MOP::Mixin';
16
17 sub method_metaclass         { $_[0]->{'method_metaclass'}            }
18 sub wrapped_method_metaclass { $_[0]->{'wrapped_method_metaclass'}    }
19 sub _meta_method_class       { 'Class::MOP::Method::Meta'             }
20
21 sub _add_meta_method {
22     my $self = shift;
23     my ($name) = @_;
24     my $existing_method = $self->can('find_method_by_name')
25                               ? $self->find_method_by_name($name)
26                               : $self->get_method($name);
27     return if $existing_method
28            && $existing_method->isa($self->_meta_method_class);
29     $self->add_method(
30         $name => $self->_meta_method_class->wrap(
31             name                 => $name,
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     $self->add_package_symbol(
86         { sigil => '&', type => 'CODE', name => $method_name },
87         $body,
88         (!defined($current_name) || $current_name =~ /^__ANON__/)
89             ? (subname => $method_name)
90             : (),
91     );
92 }
93
94 sub _code_is_mine {
95     my ( $self, $code ) = @_;
96
97     my ( $code_package, $code_name ) = Class::MOP::get_code_info($code);
98
99     return $code_package && $code_package eq $self->name
100         || ( $code_package eq 'constant' && $code_name eq '__ANON__' );
101 }
102
103 sub has_method {
104     my ( $self, $method_name ) = @_;
105
106     ( defined $method_name && length $method_name )
107         || confess "You must define a method name";
108
109     return defined( $self->_get_maybe_raw_method($method_name) );
110 }
111
112 sub get_method {
113     my ( $self, $method_name ) = @_;
114
115     ( defined $method_name && length $method_name )
116         || confess "You must define a method name";
117
118     my $method = $self->_get_maybe_raw_method($method_name)
119         or return;
120
121     return $method if blessed $method;
122
123     return $self->_method_map->{$method_name} = $self->wrap_method_body(
124         body                 => $method,
125         name                 => $method_name,
126         associated_metaclass => $self,
127     );
128 }
129
130 sub _get_maybe_raw_method {
131     my ( $self, $method_name ) = @_;
132
133     my $method_map = $self->_method_map;
134     my $map_entry  = $method_map->{$method_name};
135     my $code       = $self->get_package_symbol(
136         {
137             name  => $method_name,
138             sigil => '&',
139             type  => 'CODE',
140         }
141     );
142
143     # The !$code case seems to happen in some weird cases where methods
144     # modifiers are added via roles or some other such bizareness. Honestly, I
145     # don't totally understand this, but returning the entry works, and keeps
146     # various MX modules from blowing up. - DR
147     return $map_entry
148         if blessed $map_entry && ( !$code || $map_entry->body == $code );
149
150     unless ($map_entry) {
151         return unless $code && $self->_code_is_mine($code);
152     }
153
154     return $code;
155 }
156
157 sub remove_method {
158     my ( $self, $method_name ) = @_;
159     ( defined $method_name && length $method_name )
160         || confess "You must define a method name";
161
162     my $removed_method = delete $self->_full_method_map->{$method_name};
163
164     $self->remove_package_symbol(
165         { sigil => '&', type => 'CODE', name => $method_name } );
166
167     $removed_method->detach_from_class
168         if $removed_method && blessed $removed_method;
169
170     # still valid, since we just removed the method from the map
171     $self->update_package_cache_flag;
172
173     return $removed_method;
174 }
175
176 sub get_method_list {
177     my $self = shift;
178
179     my $namespace = $self->namespace;
180
181     # Constants may show up as some sort of non-GLOB reference in the
182     # namespace hash ref, depending on the Perl version.
183     return grep {
184         defined $namespace->{$_}
185             && ( ref( \$namespace->{$_} ) ne 'GLOB'
186             || *{ $namespace->{$_} }{CODE} )
187             && $self->has_method($_)
188         }
189         keys %{$namespace};
190 }
191
192 # This should probably be what get_method_list actually does, instead of just
193 # returning names. This was created as a much faster alternative to
194 # $meta->get_method($_) for $meta->get_method_list
195 sub _get_local_methods {
196     my $self = shift;
197
198     my $namespace = $self->namespace;
199
200     return map { $self->get_method($_) }
201         grep {
202         defined $namespace->{$_}
203             && ( ref $namespace->{$_}
204             || *{ $namespace->{$_} }{CODE} )
205         }
206         keys %{$namespace};
207 }
208
209 sub _restore_metamethods_from {
210     my $self = shift;
211     my ($old_meta) = @_;
212
213     for my $method ($old_meta->_get_local_methods) {
214         $method->_make_compatible_with($self->method_metaclass);
215         $self->add_method($method->name => $method);
216     }
217 }
218
219 1;
220
221 __END__
222
223 =pod
224
225 =head1 NAME
226
227 Class::MOP::Mixin::HasMethods - Methods for metaclasses which have methods
228
229 =head1 DESCRIPTION
230
231 This class implements methods for metaclasses which have methods
232 (L<Class::MOP::Package> and L<Moose::Meta::Role>). See L<Class::MOP::Package>
233 for API details.
234
235 =head1 AUTHORS
236
237 Dave Rolsky E<lt>autarch@urth.orgE<gt>
238
239 =head1 COPYRIGHT AND LICENSE
240
241 Copyright 2006-2010 by Infinity Interactive, Inc.
242
243 L<http://www.iinteractive.com>
244
245 This library is free software; you can redistribute it and/or modify
246 it under the same terms as Perl itself.
247
248 =cut