Add a can_be_inlined method to CMOP::Method::Constructor which we
[gitmo/Class-MOP.git] / lib / Class / MOP / Immutable.pm
CommitLineData
c23184fc 1
2package Class::MOP::Immutable;
3
4use strict;
5use warnings;
6
7use Class::MOP::Method::Constructor;
8
9use Carp 'confess';
10use Scalar::Util 'blessed';
11
34147f49 12our $VERSION = '0.71_01';
d519662a 13$VERSION = eval $VERSION;
c23184fc 14our $AUTHORITY = 'cpan:STEVAN';
15
d7b2249e 16use base 'Class::MOP::Object';
17
0ac992ee 18sub new {
1ae8e211 19 my ($class, @args) = @_;
0ac992ee 20
1ae8e211 21 my ( $metaclass, $options );
22
23 if ( @args == 2 ) {
24 # compatibility args
25 ( $metaclass, $options ) = @args;
26 } else {
27 unshift @args, "metaclass" if @args % 2 == 1;
28
29 # default named args
30 my %options = @args;
31 $options = \%options;
32 $metaclass = $options{metaclass};
33 }
34
0bfc85b8 35 my $self = $class->_new(
8683db0e 36 'metaclass' => $metaclass,
37 'options' => $options,
38 'immutable_metaclass' => undef,
0bfc85b8 39 );
0ac992ee 40
c23184fc 41 return $self;
42}
43
0bfc85b8 44sub _new {
45 my $class = shift;
46 my $options = @_ == 1 ? $_[0] : {@_};
47
48 bless $options, $class;
49}
50
76c20e30 51sub immutable_metaclass {
52 my $self = shift;
53
54 $self->create_immutable_metaclass unless $self->{'immutable_metaclass'};
55
56 return $self->{'immutable_metaclass'};
57}
58
8683db0e 59sub metaclass { (shift)->{'metaclass'} }
60sub options { (shift)->{'options'} }
c23184fc 61
62sub create_immutable_metaclass {
63 my $self = shift;
64
65 # NOTE:
0ac992ee 66 # The immutable version of the
c23184fc 67 # metaclass is just a anon-class
0ac992ee 68 # which shadows the methods
c23184fc 69 # appropriately
8683db0e 70 $self->{'immutable_metaclass'} = Class::MOP::Class->create_anon_class(
c23184fc 71 superclasses => [ blessed($self->metaclass) ],
72 methods => $self->create_methods_for_immutable_metaclass,
0ac992ee 73 );
c23184fc 74}
75
d9586da2 76
c23184fc 77my %DEFAULT_METHODS = (
d9586da2 78 # I don't really understand this, but removing it breaks tests (groditi)
0ac992ee 79 meta => sub {
c23184fc 80 my $self = shift;
0ac992ee 81 # if it is not blessed, then someone is asking
127d39a7 82 # for the meta of Class::MOP::Immutable
c23184fc 83 return Class::MOP::Class->initialize($self) unless blessed($self);
0ac992ee 84 # otherwise, they are asking for the metaclass
c23184fc 85 # which has been made immutable, which is itself
84bc89b3 86 # except in the cases where it is a metaclass itself
87 # that has been made immutable and for that we need
88 # to dig a bit ...
89 if ($self->isa('Class::MOP::Class')) {
90 return $self->{'___original_class'}->meta;
91 }
92 else {
93 return $self;
94 }
c23184fc 95 },
d9586da2 96 is_mutable => sub { 0 },
97 is_immutable => sub { 1 },
98 make_immutable => sub { () },
c23184fc 99);
100
101# NOTE:
0ac992ee 102# this will actually convert the
103# existing metaclass to an immutable
c23184fc 104# version of itself
105sub make_metaclass_immutable {
229910b5 106 my ($self, $metaclass, $options) = @_;
107
1a84e3f3 108 my %options = (
109 inline_accessors => 1,
110 inline_constructor => 1,
111 inline_destructor => 0,
112 constructor_name => 'new',
113 debug => 0,
114 %$options,
115 );
116
117 %$options = %options; # FIXME who the hell is relying on this?!? tests fail =(
0ac992ee 118
75f173e5 119 $self->_inline_accessors( $metaclass, \%options );
120 $self->_inline_constructor( $metaclass, \%options );
121 $self->_inline_destructor( $metaclass, \%options );
bc79f8a3 122 $self->_check_memoized_methods( $metaclass, \%options );
75f173e5 123
124 $metaclass->{'___original_class'} = blessed($metaclass);
125 bless $metaclass => $self->immutable_metaclass->name;
126}
c23184fc 127
75f173e5 128sub _inline_accessors {
129 my ( $self, $metaclass, $options ) = @_;
130
131 return unless $options->{inline_accessors};
132
133 foreach my $attr_name ( $metaclass->get_attribute_list ) {
134 $metaclass->get_attribute($attr_name)->install_accessors(1);
0ac992ee 135 }
75f173e5 136}
0ac992ee 137
75f173e5 138sub _inline_constructor {
139 my ( $self, $metaclass, $options ) = @_;
140
141 return unless $options->{inline_constructor};
142
2690a5c0 143 return
144 unless $options->{replace_constructor}
145 or !$metaclass->has_method( $options->{constructor_name} );
146
75f173e5 147 my $constructor_class = $options->{constructor_class}
148 || 'Class::MOP::Method::Constructor';
2690a5c0 149
f0de47d9 150 my $constructor = $constructor_class->new(
151 options => $options,
152 metaclass => $metaclass,
153 is_inline => 1,
154 package_name => $metaclass->name,
155 name => $options->{constructor_name},
2690a5c0 156 );
157
f0de47d9 158 $metaclass->add_method( $options->{constructor_name} => $constructor )
159 if $constructor->can_be_inlined;
75f173e5 160}
161
162sub _inline_destructor {
163 my ( $self, $metaclass, $options ) = @_;
164
165 return unless $options->{inline_destructor};
166
167 ( exists $options->{destructor_class} )
168 || confess "The 'inline_destructor' option is present, but "
169 . "no destructor class was specified";
170
171 my $destructor_class = $options->{destructor_class};
172
2690a5c0 173 return unless $destructor_class->is_needed($metaclass);
75f173e5 174
2690a5c0 175 my $destructor = $destructor_class->new(
176 options => $options,
177 metaclass => $metaclass,
178 package_name => $metaclass->name,
179 name => 'DESTROY'
180 );
181
182 return unless $destructor->is_needed;
183
184 $metaclass->add_method( 'DESTROY' => $destructor )
75f173e5 185}
186
bc79f8a3 187sub _check_memoized_methods {
75f173e5 188 my ( $self, $metaclass, $options ) = @_;
0ac992ee 189
c23184fc 190 my $memoized_methods = $self->options->{memoize};
75f173e5 191 foreach my $method_name ( keys %{$memoized_methods} ) {
c23184fc 192 my $type = $memoized_methods->{$method_name};
0ac992ee 193
75f173e5 194 ( $metaclass->can($method_name) )
195 || confess "Could not find the method '$method_name' in "
196 . $metaclass->name;
0ac992ee 197 }
c23184fc 198}
199
fd93a7b6 200sub create_methods_for_immutable_metaclass {
201 my $self = shift;
202
203 my %methods = %DEFAULT_METHODS;
204 my $metaclass = $self->metaclass;
205 my $meta = $metaclass->meta;
206
207 $methods{get_mutable_metaclass_name}
208 = sub { (shift)->{'___original_class'} };
209
210 $methods{immutable_transformer} = sub {$self};
211
212 return {
213 %DEFAULT_METHODS,
214 $self->_make_read_only_methods( $metaclass, $meta ),
215 $self->_make_uncallable_methods( $metaclass, $meta ),
216 $self->_make_memoized_methods( $metaclass, $meta ),
217 $self->_make_wrapped_methods( $metaclass, $meta ),
218 get_mutable_metaclass_name => sub { (shift)->{'___original_class'} },
219 immutable_transformer => sub {$self},
220 };
221}
222
223sub _make_read_only_methods {
224 my ( $self, $metaclass, $meta ) = @_;
225
226 my %methods;
227 foreach my $read_only_method ( @{ $self->options->{read_only} } ) {
228 my $method = $meta->find_method_by_name($read_only_method);
229
230 ( defined $method )
231 || confess "Could not find the method '$read_only_method' in "
232 . $metaclass->name;
233
234 $methods{$read_only_method} = sub {
235 confess "This method is read-only" if scalar @_ > 1;
236 goto &{ $method->body };
237 };
238 }
239
240 return %methods;
241}
242
243sub _make_uncallable_methods {
244 my ( $self, $metaclass, $meta ) = @_;
245
246 my %methods;
247 foreach my $cannot_call_method ( @{ $self->options->{cannot_call} } ) {
248 $methods{$cannot_call_method} = sub {
249 confess
250 "This method ($cannot_call_method) cannot be called on an immutable instance";
251 };
252 }
253
254 return %methods;
255}
256
257sub _make_memoized_methods {
258 my ( $self, $metaclass, $meta ) = @_;
259
260 my %methods;
261
262 my $memoized_methods = $self->options->{memoize};
263 foreach my $method_name ( keys %{$memoized_methods} ) {
264 my $type = $memoized_methods->{$method_name};
265 my $key = '___' . $method_name;
266 my $method = $meta->find_method_by_name($method_name);
267
268 if ( $type eq 'ARRAY' ) {
269 $methods{$method_name} = sub {
270 @{ $_[0]->{$key} } = $method->execute( $_[0] )
271 if !exists $_[0]->{$key};
272 return @{ $_[0]->{$key} };
273 };
274 }
275 elsif ( $type eq 'HASH' ) {
276 $methods{$method_name} = sub {
277 %{ $_[0]->{$key} } = $method->execute( $_[0] )
278 if !exists $_[0]->{$key};
279 return %{ $_[0]->{$key} };
280 };
281 }
282 elsif ( $type eq 'SCALAR' ) {
283 $methods{$method_name} = sub {
284 $_[0]->{$key} = $method->execute( $_[0] )
285 if !exists $_[0]->{$key};
286 return $_[0]->{$key};
287 };
288 }
289 }
290
291 return %methods;
292}
293
294sub _make_wrapped_methods {
295 my ( $self, $metaclass, $meta ) = @_;
296
297 my %methods;
298
299 my $wrapped_methods = $self->options->{wrapped};
300
301 foreach my $method_name ( keys %{$wrapped_methods} ) {
302 my $method = $meta->find_method_by_name($method_name);
303
304 ( defined $method )
305 || confess "Could not find the method '$method_name' in "
306 . $metaclass->name;
307
308 my $wrapper = $wrapped_methods->{$method_name};
309
310 $methods{$method_name} = sub { $wrapper->( $method, @_ ) };
311 }
312
313 return %methods;
314}
315
0ac992ee 316sub make_metaclass_mutable {
229910b5 317 my ($self, $immutable, $options) = @_;
318
319 my %options = %$options;
0ac992ee 320
321 my $original_class = $immutable->get_mutable_metaclass_name;
322 delete $immutable->{'___original_class'} ;
323 bless $immutable => $original_class;
324
325 my $memoized_methods = $self->options->{memoize};
326 foreach my $method_name (keys %{$memoized_methods}) {
327 my $type = $memoized_methods->{$method_name};
328
329 ($immutable->can($method_name))
330 || confess "Could not find the method '$method_name' in " . $immutable->name;
331 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
332 delete $immutable->{'___' . $method_name};
333 }
334 }
335
336 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
337 $immutable->remove_method('DESTROY')
11b56828 338 if blessed($immutable->get_method('DESTROY')) eq $options{destructor_class};
0ac992ee 339 }
340
b817e248 341 # NOTE:
342 # 14:01 <@stevan> nah,. you shouldnt
343 # 14:01 <@stevan> they are just inlined
344 # 14:01 <@stevan> which is the default in Moose anyway
345 # 14:02 <@stevan> and adding new attributes will just DWIM
346 # 14:02 <@stevan> and you really cant change an attribute anyway
347 # if ($options{inline_accessors}) {
348 # foreach my $attr_name ($immutable->get_attribute_list) {
349 # my $attr = $immutable->get_attribute($attr_name);
350 # $attr->remove_accessors;
351 # $attr->install_accessors(0);
352 # }
353 # }
354
355 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
356 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
357 # 14:27 <@stevan> so I am not worried
11b56828 358 if ($options{inline_constructor} && $immutable->has_method($options{constructor_name})) {
0ac992ee 359 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
360 $immutable->remove_method( $options{constructor_name} )
11b56828 361 if blessed($immutable->get_method($options{constructor_name})) eq $constructor_class;
0ac992ee 362 }
363}
364
c23184fc 3651;
366
367__END__
368
369=pod
370
0ac992ee 371=head1 NAME
c23184fc 372
373Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
374
375=head1 SYNOPSIS
376
96e38ba6 377 use Class::MOP::Immutable;
0ac992ee 378
96e38ba6 379 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
380 read_only => [qw/superclasses/],
381 cannot_call => [qw/
382 add_method
383 alias_method
384 remove_method
385 add_attribute
386 remove_attribute
387 add_package_symbol
0ac992ee 388 remove_package_symbol
96e38ba6 389 /],
390 memoize => {
391 class_precedence_list => 'ARRAY',
0ac992ee 392 compute_all_applicable_attributes => 'ARRAY',
393 get_meta_instance => 'SCALAR',
394 get_method_map => 'SCALAR',
96e38ba6 395 }
0ac992ee 396 });
96e38ba6 397
398 $immutable_metaclass->make_metaclass_immutable(@_)
399
c23184fc 400=head1 DESCRIPTION
401
0ac992ee 402This is basically a module for applying a transformation on a given
403metaclass. Current features include making methods read-only,
96e38ba6 404making methods un-callable and memoizing methods (in a type specific
0ac992ee 405way too).
96e38ba6 406
127d39a7 407This module is not for the feint of heart, it does some whacky things
408to the metaclass in order to make it immutable. If you are just curious,
409I suggest you turn back now, there is nothing to see here.
96e38ba6 410
c23184fc 411=head1 METHODS
412
413=over 4
414
96e38ba6 415=item B<new ($metaclass, \%options)>
416
0ac992ee 417Given a C<$metaclass> and a set of C<%options> this module will
418prepare an immutable version of the C<$metaclass>, which can then
419be applied to the C<$metaclass> using the C<make_metaclass_immutable>
96e38ba6 420method.
421
c23184fc 422=item B<options>
423
96e38ba6 424Returns the options HASH set in C<new>.
425
c23184fc 426=item B<metaclass>
427
96e38ba6 428Returns the metaclass set in C<new>.
429
c23184fc 430=item B<immutable_metaclass>
431
96e38ba6 432Returns the immutable metaclass created within C<new>.
433
c23184fc 434=back
435
436=over 4
437
438=item B<create_immutable_metaclass>
439
0ac992ee 440This will create the immutable version of the C<$metaclass>, but will
441not actually change the original metaclass.
96e38ba6 442
c23184fc 443=item B<create_methods_for_immutable_metaclass>
444
0ac992ee 445This will create all the methods for the immutable metaclass based
96e38ba6 446on the C<%options> passed into C<new>.
447
0ac992ee 448=item B<make_metaclass_immutable (%options)>
c23184fc 449
96e38ba6 450This will actually change the C<$metaclass> into the immutable version.
451
0ac992ee 452=item B<make_metaclass_mutable (%options)>
453
454This will change the C<$metaclass> into the mutable version by reversing
455the immutable process. C<%options> should be the same options that were
456given to make_metaclass_immutable.
457
c23184fc 458=back
459
460=head1 AUTHORS
461
462Stevan Little E<lt>stevan@iinteractive.comE<gt>
463
464=head1 COPYRIGHT AND LICENSE
465
69e3ab0a 466Copyright 2006-2008 by Infinity Interactive, Inc.
c23184fc 467
468L<http://www.iinteractive.com>
469
470This library is free software; you can redistribute it and/or modify
0ac992ee 471it under the same terms as Perl itself.
c23184fc 472
473=cut