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