We are never going to make a destructor object and then find out it
[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
5f9d7960 12our $VERSION = '0.78_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,
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
2690a5c0 186 $metaclass->add_method( 'DESTROY' => $destructor )
75f173e5 187}
188
bc79f8a3 189sub _check_memoized_methods {
75f173e5 190 my ( $self, $metaclass, $options ) = @_;
0ac992ee 191
c23184fc 192 my $memoized_methods = $self->options->{memoize};
75f173e5 193 foreach my $method_name ( keys %{$memoized_methods} ) {
c23184fc 194 my $type = $memoized_methods->{$method_name};
0ac992ee 195
75f173e5 196 ( $metaclass->can($method_name) )
197 || confess "Could not find the method '$method_name' in "
198 . $metaclass->name;
0ac992ee 199 }
c23184fc 200}
201
fd93a7b6 202sub create_methods_for_immutable_metaclass {
203 my $self = shift;
204
205 my %methods = %DEFAULT_METHODS;
206 my $metaclass = $self->metaclass;
207 my $meta = $metaclass->meta;
208
209 $methods{get_mutable_metaclass_name}
210 = sub { (shift)->{'___original_class'} };
211
212 $methods{immutable_transformer} = sub {$self};
213
214 return {
215 %DEFAULT_METHODS,
216 $self->_make_read_only_methods( $metaclass, $meta ),
217 $self->_make_uncallable_methods( $metaclass, $meta ),
218 $self->_make_memoized_methods( $metaclass, $meta ),
219 $self->_make_wrapped_methods( $metaclass, $meta ),
220 get_mutable_metaclass_name => sub { (shift)->{'___original_class'} },
221 immutable_transformer => sub {$self},
222 };
223}
224
225sub _make_read_only_methods {
226 my ( $self, $metaclass, $meta ) = @_;
227
228 my %methods;
229 foreach my $read_only_method ( @{ $self->options->{read_only} } ) {
230 my $method = $meta->find_method_by_name($read_only_method);
231
232 ( defined $method )
233 || confess "Could not find the method '$read_only_method' in "
234 . $metaclass->name;
235
236 $methods{$read_only_method} = sub {
237 confess "This method is read-only" if scalar @_ > 1;
238 goto &{ $method->body };
239 };
240 }
241
242 return %methods;
243}
244
245sub _make_uncallable_methods {
246 my ( $self, $metaclass, $meta ) = @_;
247
248 my %methods;
249 foreach my $cannot_call_method ( @{ $self->options->{cannot_call} } ) {
250 $methods{$cannot_call_method} = sub {
251 confess
252 "This method ($cannot_call_method) cannot be called on an immutable instance";
253 };
254 }
255
256 return %methods;
257}
258
259sub _make_memoized_methods {
260 my ( $self, $metaclass, $meta ) = @_;
261
262 my %methods;
263
264 my $memoized_methods = $self->options->{memoize};
265 foreach my $method_name ( keys %{$memoized_methods} ) {
266 my $type = $memoized_methods->{$method_name};
267 my $key = '___' . $method_name;
268 my $method = $meta->find_method_by_name($method_name);
269
270 if ( $type eq 'ARRAY' ) {
271 $methods{$method_name} = sub {
272 @{ $_[0]->{$key} } = $method->execute( $_[0] )
273 if !exists $_[0]->{$key};
274 return @{ $_[0]->{$key} };
275 };
276 }
277 elsif ( $type eq 'HASH' ) {
278 $methods{$method_name} = sub {
279 %{ $_[0]->{$key} } = $method->execute( $_[0] )
280 if !exists $_[0]->{$key};
281 return %{ $_[0]->{$key} };
282 };
283 }
284 elsif ( $type eq 'SCALAR' ) {
285 $methods{$method_name} = sub {
286 $_[0]->{$key} = $method->execute( $_[0] )
287 if !exists $_[0]->{$key};
288 return $_[0]->{$key};
289 };
290 }
291 }
292
293 return %methods;
294}
295
296sub _make_wrapped_methods {
297 my ( $self, $metaclass, $meta ) = @_;
298
299 my %methods;
300
301 my $wrapped_methods = $self->options->{wrapped};
302
303 foreach my $method_name ( keys %{$wrapped_methods} ) {
304 my $method = $meta->find_method_by_name($method_name);
305
306 ( defined $method )
307 || confess "Could not find the method '$method_name' in "
308 . $metaclass->name;
309
310 my $wrapper = $wrapped_methods->{$method_name};
311
312 $methods{$method_name} = sub { $wrapper->( $method, @_ ) };
313 }
314
315 return %methods;
316}
317
0ac992ee 318sub make_metaclass_mutable {
229910b5 319 my ($self, $immutable, $options) = @_;
320
321 my %options = %$options;
0ac992ee 322
323 my $original_class = $immutable->get_mutable_metaclass_name;
324 delete $immutable->{'___original_class'} ;
325 bless $immutable => $original_class;
326
327 my $memoized_methods = $self->options->{memoize};
328 foreach my $method_name (keys %{$memoized_methods}) {
329 my $type = $memoized_methods->{$method_name};
330
331 ($immutable->can($method_name))
332 || confess "Could not find the method '$method_name' in " . $immutable->name;
333 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
334 delete $immutable->{'___' . $method_name};
335 }
336 }
337
338 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
339 $immutable->remove_method('DESTROY')
11b56828 340 if blessed($immutable->get_method('DESTROY')) eq $options{destructor_class};
0ac992ee 341 }
342
b817e248 343 # NOTE:
344 # 14:01 <@stevan> nah,. you shouldnt
345 # 14:01 <@stevan> they are just inlined
346 # 14:01 <@stevan> which is the default in Moose anyway
347 # 14:02 <@stevan> and adding new attributes will just DWIM
348 # 14:02 <@stevan> and you really cant change an attribute anyway
349 # if ($options{inline_accessors}) {
350 # foreach my $attr_name ($immutable->get_attribute_list) {
351 # my $attr = $immutable->get_attribute($attr_name);
352 # $attr->remove_accessors;
353 # $attr->install_accessors(0);
354 # }
355 # }
356
357 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
358 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
359 # 14:27 <@stevan> so I am not worried
11b56828 360 if ($options{inline_constructor} && $immutable->has_method($options{constructor_name})) {
0ac992ee 361 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
c1809cb1 362
363 if ( blessed($immutable->get_method($options{constructor_name})) eq $constructor_class ) {
364 $immutable->remove_method( $options{constructor_name} );
ec845081 365 $self->{inlined_constructor} = undef;
c1809cb1 366 }
0ac992ee 367 }
368}
369
c23184fc 3701;
371
372__END__
373
374=pod
375
0ac992ee 376=head1 NAME
c23184fc 377
378Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
379
380=head1 SYNOPSIS
381
96e38ba6 382 use Class::MOP::Immutable;
0ac992ee 383
96e38ba6 384 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
385 read_only => [qw/superclasses/],
386 cannot_call => [qw/
387 add_method
388 alias_method
389 remove_method
390 add_attribute
391 remove_attribute
392 add_package_symbol
0ac992ee 393 remove_package_symbol
96e38ba6 394 /],
395 memoize => {
396 class_precedence_list => 'ARRAY',
0ac992ee 397 compute_all_applicable_attributes => 'ARRAY',
398 get_meta_instance => 'SCALAR',
399 get_method_map => 'SCALAR',
96e38ba6 400 }
0ac992ee 401 });
96e38ba6 402
403 $immutable_metaclass->make_metaclass_immutable(@_)
404
c23184fc 405=head1 DESCRIPTION
406
1407d471 407This class encapsulates the logic behind immutabilization.
96e38ba6 408
1407d471 409This class provides generic immutabilization logic. Decisions about
410I<what> gets transformed are up to the caller.
411
412Immutabilization allows for a number of transformations. It can ask
413the calling metaclass to inline methods such as the constructor,
414destructor, or accessors. It can memoize metaclass accessors
415themselves. It can also turn read-write accessors in the metaclass
416into read-only methods, and make attempting to set these values an
417error. Finally, it can make some methods throw an exception when they
418are called. This is used to disable methods that can alter the class.
96e38ba6 419
c23184fc 420=head1 METHODS
421
422=over 4
423
1407d471 424=item B<< Class::MOP::Immutable->new($metaclass, %options) >>
96e38ba6 425
1407d471 426This method takes a metaclass object (typically a L<Class::MOP::Class>
427object) and a hash of options.
96e38ba6 428
1407d471 429It returns a new transformer, but does not actually do any
430transforming yet.
c23184fc 431
1407d471 432This method accepts the following options:
96e38ba6 433
1407d471 434=over 8
c23184fc 435
1407d471 436=item * inline_accessors
96e38ba6 437
1407d471 438=item * inline_constructor
c23184fc 439
1407d471 440=item * inline_destructor
96e38ba6 441
1407d471 442These are all booleans indicating whether the specified method(s)
443should be inlined.
c23184fc 444
1407d471 445By default, accessors and the constructor are inlined, but not the
446destructor.
447
448=item * replace_constructor
449
450This is a boolean indicating whether an existing constructor should be
451replaced when inlining a constructor. This defaults to false.
452
453=item * constructor_name
454
455This is the constructor method name. This defaults to "new".
456
457=item * constructor_class
458
459The name of the method metaclass for constructors. It will be used to
460generate the inlined constructor. This defaults to
461"Class::MOP::Method::Constructor".
462
463=item * destructor_class
c23184fc 464
1407d471 465The name of the method metaclass for destructors. It will be used to
466generate the inlined destructor. This defaults to
467"Class::MOP::Method::Denstructor".
c23184fc 468
1407d471 469=item * memoize
470
471This option takes a hash reference. They keys are method names to be
472memoized, and the values are the type of data the method returns. This
473can be one of "SCALAR", "ARRAY", or "HASH".
474
475=item * read_only
476
477This option takes an array reference of read-write methods which will
478be made read-only. After they are transformed, attempting to set them
479will throw an error.
480
481=item * cannot_call
482
483This option takes an array reference of methods which cannot be called
484after immutabilization. Attempting to call these methods will throw an
485error.
486
487=item * wrapped
488
489This option takes a hash reference. The keys are method names and the
490body is a subroutine reference which will wrap the named method. This
491allows you to do some sort of custom transformation to a method.
492
493=back
96e38ba6 494
1407d471 495=item B<< $transformer->options >>
c23184fc 496
1407d471 497Returns a hash reference of the options passed to C<new>.
96e38ba6 498
1407d471 499=item B<< $transformer->metaclass >>
c23184fc 500
1407d471 501Returns the metaclass object passed to C<new>.
96e38ba6 502
1407d471 503=item B<< $transformer->immutable_metaclass >>
0ac992ee 504
1407d471 505Returns the immutable metaclass object that is created by the
506transformation process.
0ac992ee 507
1407d471 508=item B<< $transformer->inlined_constructor >>
c1809cb1 509
ec845081 510If the constructor was inlined, this returns the constructor method
511object that was created to do this.
512
c23184fc 513=back
514
515=head1 AUTHORS
516
517Stevan Little E<lt>stevan@iinteractive.comE<gt>
518
519=head1 COPYRIGHT AND LICENSE
520
070bb6c9 521Copyright 2006-2009 by Infinity Interactive, Inc.
c23184fc 522
523L<http://www.iinteractive.com>
524
525This library is free software; you can redistribute it and/or modify
0ac992ee 526it under the same terms as Perl itself.
c23184fc 527
528=cut