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