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