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