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