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