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