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