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