proper API docs for CMOP::Immutable
[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
eca95e04 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
2ca3697e 409This class encapsulates the logic behind immutabilization.
96e38ba6 410
2ca3697e 411This class provides generic immutabilization logic. Decisions about
412I<what> gets transformed are up to the caller.
413
414Immutabilization allows for a number of transformations. It can ask
415the calling metaclass to inline methods such as the constructor,
416destructor, or accessors. It can memoize metaclass accessors
417themselves. It can also turn read-write accessors in the metaclass
418into read-only methods, and make attempting to set these values an
419error. Finally, it can make some methods throw an exception when they
420are called. This is used to disable methods that can alter the class.
96e38ba6 421
c23184fc 422=head1 METHODS
423
424=over 4
425
2ca3697e 426=item B<< Class::MOP::Immutable->new($metaclass, %options) >>
96e38ba6 427
2ca3697e 428This method takes a metaclass object (typically a L<Class::MOP::Class>
429object) and a hash of options.
96e38ba6 430
2ca3697e 431It returns a new transformer, but does not actually do any
432transforming yet.
c23184fc 433
2ca3697e 434This method accepts the following options:
96e38ba6 435
2ca3697e 436=over 8
c23184fc 437
2ca3697e 438=item * inline_accessors
96e38ba6 439
2ca3697e 440=item * inline_constructor
c23184fc 441
2ca3697e 442=item * inline_destructor
96e38ba6 443
2ca3697e 444These are all booleans indicating whether the specified method(s)
445should be inlined.
c23184fc 446
2ca3697e 447By default, accessors and the constructor are inlined, but not the
448destructor.
449
450=item * replace_constructor
451
452This is a boolean indicating whether an existing constructor should be
453replaced when inlining a constructor. This defaults to false.
454
455=item * constructor_name
456
457This is the constructor method name. This defaults to "new".
458
459=item * constructor_class
460
461The name of the method metaclass for constructors. It will be used to
462generate the inlined constructor. This defaults to
463"Class::MOP::Method::Constructor".
464
465=item * destructor_class
c23184fc 466
2ca3697e 467The name of the method metaclass for destructors. It will be used to
468generate the inlined destructor. This defaults to
469"Class::MOP::Method::Denstructor".
c23184fc 470
2ca3697e 471=item * memoize
472
473This option takes a hash reference. They keys are method names to be
474memoized, and the values are the type of data the method returns. This
475can be one of "SCALAR", "ARRAY", or "HASH".
476
477=item * read_only
478
479This option takes an array reference of read-write methods which will
480be made read-only. After they are transformed, attempting to set them
481will throw an error.
482
483=item * cannot_call
484
485This option takes an array reference of methods which cannot be called
486after immutabilization. Attempting to call these methods will throw an
487error.
488
489=item * wrapped
490
491This option takes a hash reference. The keys are method names and the
492body is a subroutine reference which will wrap the named method. This
493allows you to do some sort of custom transformation to a method.
494
495=back
96e38ba6 496
2ca3697e 497=item B<< $transformer->options >>
c23184fc 498
2ca3697e 499Returns a hash reference of the options passed to C<new>.
96e38ba6 500
2ca3697e 501=item B<< $transformer->metaclass >>
c23184fc 502
2ca3697e 503Returns the metaclass object passed to C<new>.
96e38ba6 504
2ca3697e 505=item B<< $transformer->immutable_metaclass >>
0ac992ee 506
2ca3697e 507Returns the immutable metaclass object that is created by the
508transformation process.
0ac992ee 509
2ca3697e 510=item B<< $transformer->inlined_constructor >>
c1809cb1 511
ec845081 512If the constructor was inlined, this returns the constructor method
513object that was created to do this.
514
c23184fc 515=back
516
517=head1 AUTHORS
518
519Stevan Little E<lt>stevan@iinteractive.comE<gt>
520
521=head1 COPYRIGHT AND LICENSE
522
070bb6c9 523Copyright 2006-2009 by Infinity Interactive, Inc.
c23184fc 524
525L<http://www.iinteractive.com>
526
527This library is free software; you can redistribute it and/or modify
0ac992ee 528it under the same terms as Perl itself.
c23184fc 529
530=cut