refactored the Constructor to support inlining better and Accessors some too
[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
b817e248 12our $VERSION = '0.02';
c23184fc 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
d9586da2 50
c23184fc 51my %DEFAULT_METHODS = (
d9586da2 52 # I don't really understand this, but removing it breaks tests (groditi)
0ac992ee 53 meta => sub {
c23184fc 54 my $self = shift;
0ac992ee 55 # if it is not blessed, then someone is asking
c23184fc 56 # for the meta of Class::MOP::Class::Immutable
57 return Class::MOP::Class->initialize($self) unless blessed($self);
0ac992ee 58 # otherwise, they are asking for the metaclass
c23184fc 59 # which has been made immutable, which is itself
60 return $self;
61 },
d9586da2 62 is_mutable => sub { 0 },
63 is_immutable => sub { 1 },
64 make_immutable => sub { () },
c23184fc 65);
66
67# NOTE:
0ac992ee 68# this will actually convert the
69# existing metaclass to an immutable
c23184fc 70# version of itself
71sub make_metaclass_immutable {
72 my ($self, $metaclass, %options) = @_;
0ac992ee 73
c23184fc 74 $options{inline_accessors} = 1 unless exists $options{inline_accessors};
75 $options{inline_constructor} = 1 unless exists $options{inline_constructor};
0ac992ee 76 $options{inline_destructor} = 0 unless exists $options{inline_destructor};
c23184fc 77 $options{constructor_name} = 'new' unless exists $options{constructor_name};
0ac992ee 78 $options{debug} = 0 unless exists $options{debug};
79
c23184fc 80 if ($options{inline_accessors}) {
81 foreach my $attr_name ($metaclass->get_attribute_list) {
82 # inline the accessors
83 $metaclass->get_attribute($attr_name)
0ac992ee 84 ->install_accessors(1);
85 }
c23184fc 86 }
87
0ac992ee 88 if ($options{inline_constructor}) {
c23184fc 89 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
c23184fc 90 $metaclass->add_method(
91 $options{constructor_name},
92 $constructor_class->new(
0ac992ee 93 options => \%options,
94 metaclass => $metaclass,
565f0cbb 95 is_inline => 1,
c23184fc 96 )
97 ) unless $metaclass->has_method($options{constructor_name});
0ac992ee 98 }
99
100 if ($options{inline_destructor}) {
c23184fc 101 (exists $options{destructor_class})
102 || confess "The 'inline_destructor' option is present, but "
103 . "no destructor class was specified";
0ac992ee 104
c23184fc 105 my $destructor_class = $options{destructor_class};
0ac992ee 106
c23184fc 107 my $destructor = $destructor_class->new(
108 options => \%options,
109 metaclass => $metaclass,
110 );
0ac992ee 111
112 $metaclass->add_method('DESTROY' => $destructor)
c23184fc 113 # NOTE:
0ac992ee 114 # we allow the destructor to determine
c23184fc 115 # if it is needed or not, it can perform
0ac992ee 116 # all sorts of checks because it has the
117 # metaclass instance
c23184fc 118 if $destructor->is_needed;
0ac992ee 119 }
120
c23184fc 121 my $memoized_methods = $self->options->{memoize};
122 foreach my $method_name (keys %{$memoized_methods}) {
123 my $type = $memoized_methods->{$method_name};
0ac992ee 124
c23184fc 125 ($metaclass->can($method_name))
0ac992ee 126 || confess "Could not find the method '$method_name' in " . $metaclass->name;
127
c23184fc 128 if ($type eq 'ARRAY') {
129 $metaclass->{'___' . $method_name} = [ $metaclass->$method_name ];
130 }
131 elsif ($type eq 'HASH') {
0ac992ee 132 $metaclass->{'___' . $method_name} = { $metaclass->$method_name };
c23184fc 133 }
134 elsif ($type eq 'SCALAR') {
135 $metaclass->{'___' . $method_name} = $metaclass->$method_name;
136 }
0ac992ee 137 }
138
0ac992ee 139 $metaclass->{'___original_class'} = blessed($metaclass);
c23184fc 140 bless $metaclass => $self->immutable_metaclass->name;
141}
142
0ac992ee 143sub make_metaclass_mutable {
144 my ($self, $immutable, %options) = @_;
145
146 my $original_class = $immutable->get_mutable_metaclass_name;
147 delete $immutable->{'___original_class'} ;
148 bless $immutable => $original_class;
149
150 my $memoized_methods = $self->options->{memoize};
151 foreach my $method_name (keys %{$memoized_methods}) {
152 my $type = $memoized_methods->{$method_name};
153
154 ($immutable->can($method_name))
155 || confess "Could not find the method '$method_name' in " . $immutable->name;
156 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
157 delete $immutable->{'___' . $method_name};
158 }
159 }
160
161 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
162 $immutable->remove_method('DESTROY')
163 if $immutable->get_method('DESTROY')->blessed eq $options{destructor_class};
164 }
165
b817e248 166 # NOTE:
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
0ac992ee 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