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