skip .gitignore
[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
c23184fc 119 if ($options{inline_accessors}) {
120 foreach my $attr_name ($metaclass->get_attribute_list) {
121 # inline the accessors
122 $metaclass->get_attribute($attr_name)
0ac992ee 123 ->install_accessors(1);
124 }
c23184fc 125 }
126
0ac992ee 127 if ($options{inline_constructor}) {
c23184fc 128 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
c23184fc 129 $metaclass->add_method(
130 $options{constructor_name},
131 $constructor_class->new(
4c105333 132 options => \%options,
133 metaclass => $metaclass,
134 is_inline => 1,
135 package_name => $metaclass->name,
136 name => $options{constructor_name}
c23184fc 137 )
6c2f6b5c 138 ) if $options{replace_constructor} or !$metaclass->has_method($options{constructor_name});
0ac992ee 139 }
140
141 if ($options{inline_destructor}) {
c23184fc 142 (exists $options{destructor_class})
143 || confess "The 'inline_destructor' option is present, but "
144 . "no destructor class was specified";
0ac992ee 145
c23184fc 146 my $destructor_class = $options{destructor_class};
0ac992ee 147
cc05f61c 148 # NOTE:
149 # we allow the destructor to determine
150 # if it is needed or not before we actually
151 # create the destructor too
152 # - SL
153 if ($destructor_class->is_needed($metaclass)) {
154 my $destructor = $destructor_class->new(
155 options => \%options,
156 metaclass => $metaclass,
157 package_name => $metaclass->name,
158 name => 'DESTROY'
159 );
160
161 $metaclass->add_method('DESTROY' => $destructor)
162 # NOTE:
163 # we allow the destructor to determine
164 # if it is needed or not, it can perform
165 # all sorts of checks because it has the
166 # metaclass instance
167 if $destructor->is_needed;
168 }
0ac992ee 169 }
170
c23184fc 171 my $memoized_methods = $self->options->{memoize};
172 foreach my $method_name (keys %{$memoized_methods}) {
173 my $type = $memoized_methods->{$method_name};
0ac992ee 174
c23184fc 175 ($metaclass->can($method_name))
0ac992ee 176 || confess "Could not find the method '$method_name' in " . $metaclass->name;
0ac992ee 177 }
178
0ac992ee 179 $metaclass->{'___original_class'} = blessed($metaclass);
c23184fc 180 bless $metaclass => $self->immutable_metaclass->name;
181}
182
0ac992ee 183sub make_metaclass_mutable {
229910b5 184 my ($self, $immutable, $options) = @_;
185
186 my %options = %$options;
0ac992ee 187
188 my $original_class = $immutable->get_mutable_metaclass_name;
189 delete $immutable->{'___original_class'} ;
190 bless $immutable => $original_class;
191
192 my $memoized_methods = $self->options->{memoize};
193 foreach my $method_name (keys %{$memoized_methods}) {
194 my $type = $memoized_methods->{$method_name};
195
196 ($immutable->can($method_name))
197 || confess "Could not find the method '$method_name' in " . $immutable->name;
198 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
199 delete $immutable->{'___' . $method_name};
200 }
201 }
202
203 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
204 $immutable->remove_method('DESTROY')
11b56828 205 if blessed($immutable->get_method('DESTROY')) eq $options{destructor_class};
0ac992ee 206 }
207
b817e248 208 # NOTE:
209 # 14:01 <@stevan> nah,. you shouldnt
210 # 14:01 <@stevan> they are just inlined
211 # 14:01 <@stevan> which is the default in Moose anyway
212 # 14:02 <@stevan> and adding new attributes will just DWIM
213 # 14:02 <@stevan> and you really cant change an attribute anyway
214 # if ($options{inline_accessors}) {
215 # foreach my $attr_name ($immutable->get_attribute_list) {
216 # my $attr = $immutable->get_attribute($attr_name);
217 # $attr->remove_accessors;
218 # $attr->install_accessors(0);
219 # }
220 # }
221
222 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
223 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
224 # 14:27 <@stevan> so I am not worried
11b56828 225 if ($options{inline_constructor} && $immutable->has_method($options{constructor_name})) {
0ac992ee 226 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
227 $immutable->remove_method( $options{constructor_name} )
11b56828 228 if blessed($immutable->get_method($options{constructor_name})) eq $constructor_class;
0ac992ee 229 }
230}
231
c23184fc 232sub create_methods_for_immutable_metaclass {
233 my $self = shift;
0ac992ee 234
c23184fc 235 my %methods = %DEFAULT_METHODS;
41885bab 236 my $metaclass = $self->metaclass;
237 my $meta = $metaclass->meta;
0ac992ee 238
c23184fc 239 foreach my $read_only_method (@{$self->options->{read_only}}) {
41885bab 240 my $method = $meta->find_method_by_name($read_only_method);
0ac992ee 241
c23184fc 242 (defined $method)
41885bab 243 || confess "Could not find the method '$read_only_method' in " . $metaclass->name;
0ac992ee 244
c23184fc 245 $methods{$read_only_method} = sub {
246 confess "This method is read-only" if scalar @_ > 1;
247 goto &{$method->body}
248 };
249 }
0ac992ee 250
c23184fc 251 foreach my $cannot_call_method (@{$self->options->{cannot_call}}) {
252 $methods{$cannot_call_method} = sub {
253 confess "This method ($cannot_call_method) cannot be called on an immutable instance";
254 };
0ac992ee 255 }
256
c23184fc 257 my $memoized_methods = $self->options->{memoize};
c23184fc 258 foreach my $method_name (keys %{$memoized_methods}) {
259 my $type = $memoized_methods->{$method_name};
9ec62360 260 my $key = '___' . $method_name;
261 my $method = $meta->find_method_by_name($method_name);
262
c23184fc 263 if ($type eq 'ARRAY') {
9ec62360 264 $methods{$method_name} = sub {
265 @{$_[0]->{$key}} = $method->execute($_[0])
266 if !exists $_[0]->{$key};
267 return @{$_[0]->{$key}};
268 };
c23184fc 269 }
270 elsif ($type eq 'HASH') {
9ec62360 271 $methods{$method_name} = sub {
272 %{$_[0]->{$key}} = $method->execute($_[0])
273 if !exists $_[0]->{$key};
274 return %{$_[0]->{$key}};
275 };
c23184fc 276 }
277 elsif ($type eq 'SCALAR') {
9ec62360 278 $methods{$method_name} = sub {
279 $_[0]->{$key} = $method->execute($_[0])
280 if !exists $_[0]->{$key};
281 return $_[0]->{$key};
282 };
0ac992ee 283 }
284 }
53299a7b 285
5f3efd66 286 my $wrapped_methods = $self->options->{wrapped};
287
288 foreach my $method_name (keys %{ $wrapped_methods }) {
41885bab 289 my $method = $meta->find_method_by_name($method_name);
5f3efd66 290
291 (defined $method)
41885bab 292 || confess "Could not find the method '$method_name' in " . $metaclass->name;
5f3efd66 293
294 my $wrapper = $wrapped_methods->{$method_name};
295
296 $methods{$method_name} = sub { $wrapper->($method, @_) };
53299a7b 297 }
0ac992ee 298
299 $methods{get_mutable_metaclass_name} = sub { (shift)->{'___original_class'} };
300
9f3ff885 301 $methods{immutable_transformer} = sub { $self };
302
c23184fc 303 return \%methods;
304}
305
3061;
307
308__END__
309
310=pod
311
0ac992ee 312=head1 NAME
c23184fc 313
314Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
315
316=head1 SYNOPSIS
317
96e38ba6 318 use Class::MOP::Immutable;
0ac992ee 319
96e38ba6 320 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
321 read_only => [qw/superclasses/],
322 cannot_call => [qw/
323 add_method
324 alias_method
325 remove_method
326 add_attribute
327 remove_attribute
328 add_package_symbol
0ac992ee 329 remove_package_symbol
96e38ba6 330 /],
331 memoize => {
332 class_precedence_list => 'ARRAY',
0ac992ee 333 compute_all_applicable_attributes => 'ARRAY',
334 get_meta_instance => 'SCALAR',
335 get_method_map => 'SCALAR',
96e38ba6 336 }
0ac992ee 337 });
96e38ba6 338
339 $immutable_metaclass->make_metaclass_immutable(@_)
340
c23184fc 341=head1 DESCRIPTION
342
0ac992ee 343This is basically a module for applying a transformation on a given
344metaclass. Current features include making methods read-only,
96e38ba6 345making methods un-callable and memoizing methods (in a type specific
0ac992ee 346way too).
96e38ba6 347
127d39a7 348This module is not for the feint of heart, it does some whacky things
349to the metaclass in order to make it immutable. If you are just curious,
350I suggest you turn back now, there is nothing to see here.
96e38ba6 351
c23184fc 352=head1 METHODS
353
354=over 4
355
96e38ba6 356=item B<new ($metaclass, \%options)>
357
0ac992ee 358Given a C<$metaclass> and a set of C<%options> this module will
359prepare an immutable version of the C<$metaclass>, which can then
360be applied to the C<$metaclass> using the C<make_metaclass_immutable>
96e38ba6 361method.
362
c23184fc 363=item B<options>
364
96e38ba6 365Returns the options HASH set in C<new>.
366
c23184fc 367=item B<metaclass>
368
96e38ba6 369Returns the metaclass set in C<new>.
370
c23184fc 371=item B<immutable_metaclass>
372
96e38ba6 373Returns the immutable metaclass created within C<new>.
374
c23184fc 375=back
376
377=over 4
378
379=item B<create_immutable_metaclass>
380
0ac992ee 381This will create the immutable version of the C<$metaclass>, but will
382not actually change the original metaclass.
96e38ba6 383
c23184fc 384=item B<create_methods_for_immutable_metaclass>
385
0ac992ee 386This will create all the methods for the immutable metaclass based
96e38ba6 387on the C<%options> passed into C<new>.
388
0ac992ee 389=item B<make_metaclass_immutable (%options)>
c23184fc 390
96e38ba6 391This will actually change the C<$metaclass> into the immutable version.
392
0ac992ee 393=item B<make_metaclass_mutable (%options)>
394
395This will change the C<$metaclass> into the mutable version by reversing
396the immutable process. C<%options> should be the same options that were
397given to make_metaclass_immutable.
398
c23184fc 399=back
400
401=head1 AUTHORS
402
403Stevan Little E<lt>stevan@iinteractive.comE<gt>
404
405=head1 COPYRIGHT AND LICENSE
406
69e3ab0a 407Copyright 2006-2008 by Infinity Interactive, Inc.
c23184fc 408
409L<http://www.iinteractive.com>
410
411This library is free software; you can redistribute it and/or modify
0ac992ee 412it under the same terms as Perl itself.
c23184fc 413
414=cut