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