No need to pass an index to an array when we can pass the array member
[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
44d6ea77 21 unshift @args, 'metaclass' if @args % 2 == 1;
22
23 my %options = (
24 inline_accessors => 1,
25 inline_constructor => 1,
26 inline_destructor => 0,
27 constructor_name => 'new',
28 constructor_class => 'Class::MOP::Method::Constructor',
29 debug => 0,
30 @args,
31 );
1ae8e211 32
0bfc85b8 33 my $self = $class->_new(
44d6ea77 34 'metaclass' => delete $options{metaclass},
35 'options' => \%options,
8683db0e 36 'immutable_metaclass' => undef,
ec845081 37 'inlined_constructor' => undef,
0bfc85b8 38 );
0ac992ee 39
c23184fc 40 return $self;
41}
42
0bfc85b8 43sub _new {
44 my $class = shift;
45 my $options = @_ == 1 ? $_[0] : {@_};
46
47 bless $options, $class;
48}
49
76c20e30 50sub immutable_metaclass {
51 my $self = shift;
52
44d6ea77 53 return $self->{'immutable_metaclass'} ||= $self->_create_immutable_metaclass;
76c20e30 54}
55
8683db0e 56sub metaclass { (shift)->{'metaclass'} }
57sub options { (shift)->{'options'} }
c1809cb1 58sub inlined_constructor { (shift)->{'inlined_constructor'} }
c23184fc 59
44d6ea77 60sub _create_immutable_metaclass {
c23184fc 61 my $self = shift;
62
44d6ea77 63 # NOTE: The immutable version of the metaclass is just a
64 # anon-class which shadows the methods appropriately
65 return Class::MOP::Class->create_anon_class(
c23184fc 66 superclasses => [ blessed($self->metaclass) ],
44d6ea77 67 methods => $self->_create_methods_for_immutable_metaclass,
0ac992ee 68 );
c23184fc 69}
70
c23184fc 71sub make_metaclass_immutable {
44d6ea77 72 my $self = shift;
1a84e3f3 73
44d6ea77 74 $self->_inline_accessors;
75 $self->_inline_constructor;
76 $self->_inline_destructor;
77 $self->_check_memoized_methods;
0ac992ee 78
44d6ea77 79 my $metaclass = $self->metaclass;
75f173e5 80
81 $metaclass->{'___original_class'} = blessed($metaclass);
82 bless $metaclass => $self->immutable_metaclass->name;
83}
c23184fc 84
75f173e5 85sub _inline_accessors {
44d6ea77 86 my $self = shift;
75f173e5 87
44d6ea77 88 return unless $self->options->{inline_accessors};
75f173e5 89
44d6ea77 90 foreach my $attr_name ( $self->metaclass->get_attribute_list ) {
91 $self->metaclass->get_attribute($attr_name)->install_accessors(1);
0ac992ee 92 }
75f173e5 93}
0ac992ee 94
75f173e5 95sub _inline_constructor {
44d6ea77 96 my $self = shift;
75f173e5 97
44d6ea77 98 return unless $self->options->{inline_constructor};
75f173e5 99
2690a5c0 100 return
44d6ea77 101 unless $self->options->{replace_constructor}
102 or !$self->metaclass->has_method(
103 $self->options->{constructor_name}
104 );
2690a5c0 105
44d6ea77 106 my $constructor_class = $self->options->{constructor_class};
2690a5c0 107
f0de47d9 108 my $constructor = $constructor_class->new(
44d6ea77 109 options => $self->options,
110 metaclass => $self->metaclass,
f0de47d9 111 is_inline => 1,
44d6ea77 112 package_name => $self->metaclass->name,
113 name => $self->options->{constructor_name},
2690a5c0 114 );
115
44d6ea77 116 if ( $self->options->{replace_constructor}
117 or $constructor->can_be_inlined ) {
118 $self->metaclass->add_method(
119 $self->options->{constructor_name} => $constructor );
ec845081 120 $self->{inlined_constructor} = $constructor;
c1809cb1 121 }
75f173e5 122}
123
124sub _inline_destructor {
44d6ea77 125 my $self = shift;
75f173e5 126
44d6ea77 127 return unless $self->options->{inline_destructor};
75f173e5 128
44d6ea77 129 ( exists $self->options->{destructor_class} )
75f173e5 130 || confess "The 'inline_destructor' option is present, but "
131 . "no destructor class was specified";
132
44d6ea77 133 my $destructor_class = $self->options->{destructor_class};
75f173e5 134
44d6ea77 135 return unless $destructor_class->is_needed( $self->metaclass );
75f173e5 136
2690a5c0 137 my $destructor = $destructor_class->new(
44d6ea77 138 options => $self->options,
139 metaclass => $self->metaclass,
140 package_name => $self->metaclass->name,
2690a5c0 141 name => 'DESTROY'
142 );
143
144 return unless $destructor->is_needed;
145
44d6ea77 146 $self->metaclass->add_method( 'DESTROY' => $destructor );
75f173e5 147}
148
bc79f8a3 149sub _check_memoized_methods {
44d6ea77 150 my $self = shift;
0ac992ee 151
c23184fc 152 my $memoized_methods = $self->options->{memoize};
75f173e5 153 foreach my $method_name ( keys %{$memoized_methods} ) {
c23184fc 154 my $type = $memoized_methods->{$method_name};
0ac992ee 155
44d6ea77 156 ( $self->metaclass->can($method_name) )
75f173e5 157 || confess "Could not find the method '$method_name' in "
44d6ea77 158 . $self->metaclass->name;
0ac992ee 159 }
c23184fc 160}
44d6ea77 161my %DEFAULT_METHODS = (
162 # I don't really understand this, but removing it breaks tests (groditi)
163 meta => sub {
164 my $self = shift;
165 # if it is not blessed, then someone is asking
166 # for the meta of Class::MOP::Immutable
167 return Class::MOP::Class->initialize($self) unless blessed($self);
168 # otherwise, they are asking for the metaclass
169 # which has been made immutable, which is itself
170 # except in the cases where it is a metaclass itself
171 # that has been made immutable and for that we need
172 # to dig a bit ...
173 if ($self->isa('Class::MOP::Class')) {
174 return $self->{'___original_class'}->meta;
175 }
176 else {
177 return $self;
178 }
179 },
180 is_mutable => sub { 0 },
181 is_immutable => sub { 1 },
182 make_immutable => sub { () },
183);
c23184fc 184
44d6ea77 185sub _create_methods_for_immutable_metaclass {
fd93a7b6 186 my $self = shift;
187
fd93a7b6 188 my $metaclass = $self->metaclass;
189 my $meta = $metaclass->meta;
190
fd93a7b6 191 return {
192 %DEFAULT_METHODS,
44d6ea77 193 $self->_make_read_only_methods,
194 $self->_make_uncallable_methods,
195 $self->_make_memoized_methods,
196 $self->_make_wrapped_methods,
fd93a7b6 197 get_mutable_metaclass_name => sub { (shift)->{'___original_class'} },
198 immutable_transformer => sub {$self},
199 };
200}
201
202sub _make_read_only_methods {
44d6ea77 203 my $self = shift;
204
205 my $metameta = $self->metaclass->meta;
fd93a7b6 206
207 my %methods;
208 foreach my $read_only_method ( @{ $self->options->{read_only} } ) {
44d6ea77 209 my $method = $metameta->find_method_by_name($read_only_method);
fd93a7b6 210
211 ( defined $method )
212 || confess "Could not find the method '$read_only_method' in "
44d6ea77 213 . $self->metaclass->name;
fd93a7b6 214
215 $methods{$read_only_method} = sub {
216 confess "This method is read-only" if scalar @_ > 1;
217 goto &{ $method->body };
218 };
219 }
220
221 return %methods;
222}
223
224sub _make_uncallable_methods {
44d6ea77 225 my $self = shift;
fd93a7b6 226
227 my %methods;
228 foreach my $cannot_call_method ( @{ $self->options->{cannot_call} } ) {
229 $methods{$cannot_call_method} = sub {
230 confess
231 "This method ($cannot_call_method) cannot be called on an immutable instance";
232 };
233 }
234
235 return %methods;
236}
237
238sub _make_memoized_methods {
44d6ea77 239 my $self = shift;
fd93a7b6 240
241 my %methods;
242
44d6ea77 243 my $metameta = $self->metaclass->meta;
244
fd93a7b6 245 my $memoized_methods = $self->options->{memoize};
246 foreach my $method_name ( keys %{$memoized_methods} ) {
247 my $type = $memoized_methods->{$method_name};
248 my $key = '___' . $method_name;
44d6ea77 249 my $method = $metameta->find_method_by_name($method_name);
fd93a7b6 250
251 if ( $type eq 'ARRAY' ) {
252 $methods{$method_name} = sub {
253 @{ $_[0]->{$key} } = $method->execute( $_[0] )
254 if !exists $_[0]->{$key};
255 return @{ $_[0]->{$key} };
256 };
257 }
258 elsif ( $type eq 'HASH' ) {
259 $methods{$method_name} = sub {
260 %{ $_[0]->{$key} } = $method->execute( $_[0] )
261 if !exists $_[0]->{$key};
262 return %{ $_[0]->{$key} };
263 };
264 }
265 elsif ( $type eq 'SCALAR' ) {
266 $methods{$method_name} = sub {
267 $_[0]->{$key} = $method->execute( $_[0] )
268 if !exists $_[0]->{$key};
269 return $_[0]->{$key};
270 };
271 }
272 }
273
274 return %methods;
275}
276
277sub _make_wrapped_methods {
44d6ea77 278 my $self = shift;
fd93a7b6 279
280 my %methods;
281
282 my $wrapped_methods = $self->options->{wrapped};
283
44d6ea77 284 my $metameta = $self->metaclass->meta;
285
fd93a7b6 286 foreach my $method_name ( keys %{$wrapped_methods} ) {
44d6ea77 287 my $method = $metameta->find_method_by_name($method_name);
fd93a7b6 288
289 ( defined $method )
290 || confess "Could not find the method '$method_name' in "
44d6ea77 291 . $self->metaclass->name;
fd93a7b6 292
293 my $wrapper = $wrapped_methods->{$method_name};
294
295 $methods{$method_name} = sub { $wrapper->( $method, @_ ) };
296 }
297
298 return %methods;
299}
300
0ac992ee 301sub make_metaclass_mutable {
44d6ea77 302 my $self = shift;
229910b5 303
44d6ea77 304 my $metaclass = $self->metaclass;
0ac992ee 305
44d6ea77 306 my $original_class = $metaclass->get_mutable_metaclass_name;
307 delete $metaclass->{'___original_class'};
308 bless $metaclass => $original_class;
0ac992ee 309
310 my $memoized_methods = $self->options->{memoize};
44d6ea77 311 foreach my $method_name ( keys %{$memoized_methods} ) {
0ac992ee 312 my $type = $memoized_methods->{$method_name};
313
44d6ea77 314 ( $metaclass->can($method_name) )
315 || confess "Could not find the method '$method_name' in "
316 . $metaclass->name;
317 if ( $type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
318 delete $metaclass->{ '___' . $method_name };
0ac992ee 319 }
320 }
321
44d6ea77 322 if ( $self->options->{inline_destructor}
323 && $metaclass->has_method('DESTROY') ) {
324 $metaclass->remove_method('DESTROY')
325 if blessed( $metaclass->get_method('DESTROY') ) eq
326 $self->options->{destructor_class};
0ac992ee 327 }
328
b817e248 329 # NOTE:
330 # 14:01 <@stevan> nah,. you shouldnt
331 # 14:01 <@stevan> they are just inlined
332 # 14:01 <@stevan> which is the default in Moose anyway
333 # 14:02 <@stevan> and adding new attributes will just DWIM
334 # 14:02 <@stevan> and you really cant change an attribute anyway
335 # if ($options{inline_accessors}) {
336 # foreach my $attr_name ($immutable->get_attribute_list) {
337 # my $attr = $immutable->get_attribute($attr_name);
338 # $attr->remove_accessors;
339 # $attr->install_accessors(0);
340 # }
341 # }
342
343 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
344 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
345 # 14:27 <@stevan> so I am not worried
44d6ea77 346 if ( $self->options->{inline_constructor}
347 && $metaclass->has_method( $self->options->{constructor_name} ) ) {
348 my $constructor_class = $self->options->{constructor_class}
349 || 'Class::MOP::Method::Constructor';
350
351 if (
352 blessed(
353 $metaclass->get_method( $self->options->{constructor_name} )
354 ) eq $constructor_class
355 ) {
356 $metaclass->remove_method( $self->options->{constructor_name} );
ec845081 357 $self->{inlined_constructor} = undef;
c1809cb1 358 }
0ac992ee 359 }
360}
361
c23184fc 3621;
363
364__END__
365
366=pod
367
0ac992ee 368=head1 NAME
c23184fc 369
370Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
371
372=head1 SYNOPSIS
373
96e38ba6 374 use Class::MOP::Immutable;
0ac992ee 375
96e38ba6 376 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
377 read_only => [qw/superclasses/],
378 cannot_call => [qw/
379 add_method
380 alias_method
381 remove_method
382 add_attribute
383 remove_attribute
384 add_package_symbol
0ac992ee 385 remove_package_symbol
96e38ba6 386 /],
387 memoize => {
388 class_precedence_list => 'ARRAY',
0ac992ee 389 compute_all_applicable_attributes => 'ARRAY',
390 get_meta_instance => 'SCALAR',
391 get_method_map => 'SCALAR',
96e38ba6 392 }
0ac992ee 393 });
96e38ba6 394
44d6ea77 395 $immutable_metaclass->make_metaclass_immutable;
96e38ba6 396
c23184fc 397=head1 DESCRIPTION
398
1407d471 399This class encapsulates the logic behind immutabilization.
96e38ba6 400
1407d471 401This class provides generic immutabilization logic. Decisions about
402I<what> gets transformed are up to the caller.
403
404Immutabilization allows for a number of transformations. It can ask
405the calling metaclass to inline methods such as the constructor,
406destructor, or accessors. It can memoize metaclass accessors
407themselves. It can also turn read-write accessors in the metaclass
408into read-only methods, and make attempting to set these values an
409error. Finally, it can make some methods throw an exception when they
410are called. This is used to disable methods that can alter the class.
96e38ba6 411
c23184fc 412=head1 METHODS
413
414=over 4
415
1407d471 416=item B<< Class::MOP::Immutable->new($metaclass, %options) >>
96e38ba6 417
1407d471 418This method takes a metaclass object (typically a L<Class::MOP::Class>
419object) and a hash of options.
96e38ba6 420
1407d471 421It returns a new transformer, but does not actually do any
422transforming yet.
c23184fc 423
1407d471 424This method accepts the following options:
96e38ba6 425
1407d471 426=over 8
c23184fc 427
1407d471 428=item * inline_accessors
96e38ba6 429
1407d471 430=item * inline_constructor
c23184fc 431
1407d471 432=item * inline_destructor
96e38ba6 433
1407d471 434These are all booleans indicating whether the specified method(s)
435should be inlined.
c23184fc 436
1407d471 437By default, accessors and the constructor are inlined, but not the
438destructor.
439
440=item * replace_constructor
441
442This is a boolean indicating whether an existing constructor should be
443replaced when inlining a constructor. This defaults to false.
444
445=item * constructor_name
446
447This is the constructor method name. This defaults to "new".
448
449=item * constructor_class
450
451The name of the method metaclass for constructors. It will be used to
452generate the inlined constructor. This defaults to
453"Class::MOP::Method::Constructor".
454
455=item * destructor_class
c23184fc 456
1407d471 457The name of the method metaclass for destructors. It will be used to
458generate the inlined destructor. This defaults to
459"Class::MOP::Method::Denstructor".
c23184fc 460
1407d471 461=item * memoize
462
463This option takes a hash reference. They keys are method names to be
464memoized, and the values are the type of data the method returns. This
465can be one of "SCALAR", "ARRAY", or "HASH".
466
467=item * read_only
468
469This option takes an array reference of read-write methods which will
470be made read-only. After they are transformed, attempting to set them
471will throw an error.
472
473=item * cannot_call
474
475This option takes an array reference of methods which cannot be called
476after immutabilization. Attempting to call these methods will throw an
477error.
478
479=item * wrapped
480
481This option takes a hash reference. The keys are method names and the
482body is a subroutine reference which will wrap the named method. This
483allows you to do some sort of custom transformation to a method.
484
485=back
96e38ba6 486
1407d471 487=item B<< $transformer->options >>
c23184fc 488
1407d471 489Returns a hash reference of the options passed to C<new>.
96e38ba6 490
1407d471 491=item B<< $transformer->metaclass >>
c23184fc 492
1407d471 493Returns the metaclass object passed to C<new>.
96e38ba6 494
1407d471 495=item B<< $transformer->immutable_metaclass >>
0ac992ee 496
1407d471 497Returns the immutable metaclass object that is created by the
498transformation process.
0ac992ee 499
1407d471 500=item B<< $transformer->inlined_constructor >>
c1809cb1 501
ec845081 502If the constructor was inlined, this returns the constructor method
503object that was created to do this.
504
c23184fc 505=back
506
507=head1 AUTHORS
508
509Stevan Little E<lt>stevan@iinteractive.comE<gt>
510
511=head1 COPYRIGHT AND LICENSE
512
070bb6c9 513Copyright 2006-2009 by Infinity Interactive, Inc.
c23184fc 514
515L<http://www.iinteractive.com>
516
517This library is free software; you can redistribute it and/or modify
0ac992ee 518it under the same terms as Perl itself.
c23184fc 519
520=cut