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