2 package Class::MOP::Immutable;
7 use Class::MOP::Method::Constructor;
10 use Scalar::Util 'blessed';
12 our $VERSION = '0.71';
13 $VERSION = eval $VERSION;
14 our $AUTHORITY = 'cpan:STEVAN';
16 use base 'Class::MOP::Object';
19 my ($class, @args) = @_;
21 my ( $metaclass, $options );
25 ( $metaclass, $options ) = @args;
27 unshift @args, "metaclass" if @args % 2 == 1;
32 $metaclass = $options{metaclass};
35 my $self = $class->_new(
36 'metaclass' => $metaclass,
37 'options' => $options,
38 'immutable_metaclass' => undef,
46 my $options = @_ == 1 ? $_[0] : {@_};
48 bless $options, $class;
51 sub immutable_metaclass {
54 $self->create_immutable_metaclass unless $self->{'immutable_metaclass'};
56 return $self->{'immutable_metaclass'};
59 sub metaclass { (shift)->{'metaclass'} }
60 sub options { (shift)->{'options'} }
62 sub create_immutable_metaclass {
66 # The immutable version of the
67 # metaclass is just a anon-class
68 # which shadows the methods
70 $self->{'immutable_metaclass'} = Class::MOP::Class->create_anon_class(
71 superclasses => [ blessed($self->metaclass) ],
72 methods => $self->create_methods_for_immutable_metaclass,
77 my %DEFAULT_METHODS = (
78 # I don't really understand this, but removing it breaks tests (groditi)
81 # if it is not blessed, then someone is asking
82 # for the meta of Class::MOP::Immutable
83 return Class::MOP::Class->initialize($self) unless blessed($self);
84 # otherwise, they are asking for the metaclass
85 # which has been made immutable, which is itself
86 # except in the cases where it is a metaclass itself
87 # that has been made immutable and for that we need
89 if ($self->isa('Class::MOP::Class')) {
90 return $self->{'___original_class'}->meta;
96 is_mutable => sub { 0 },
97 is_immutable => sub { 1 },
98 make_immutable => sub { () },
102 # this will actually convert the
103 # existing metaclass to an immutable
105 sub make_metaclass_immutable {
106 my ($self, $metaclass, $options) = @_;
109 inline_accessors => 1,
110 inline_constructor => 1,
111 inline_destructor => 0,
112 constructor_name => 'new',
117 %$options = %options; # FIXME who the hell is relying on this?!? tests fail =(
119 if ($options{inline_accessors}) {
120 foreach my $attr_name ($metaclass->get_attribute_list) {
121 # inline the accessors
122 $metaclass->get_attribute($attr_name)
123 ->install_accessors(1);
127 if ($options{inline_constructor}) {
128 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
129 $metaclass->add_method(
130 $options{constructor_name},
131 $constructor_class->new(
132 options => \%options,
133 metaclass => $metaclass,
135 package_name => $metaclass->name,
136 name => $options{constructor_name}
138 ) if $options{replace_constructor} or !$metaclass->has_method($options{constructor_name});
141 if ($options{inline_destructor}) {
142 (exists $options{destructor_class})
143 || confess "The 'inline_destructor' option is present, but "
144 . "no destructor class was specified";
146 my $destructor_class = $options{destructor_class};
149 # we allow the destructor to determine
150 # if it is needed or not before we actually
151 # create the destructor too
153 if ($destructor_class->is_needed($metaclass)) {
154 my $destructor = $destructor_class->new(
155 options => \%options,
156 metaclass => $metaclass,
157 package_name => $metaclass->name,
161 $metaclass->add_method('DESTROY' => $destructor)
163 # we allow the destructor to determine
164 # if it is needed or not, it can perform
165 # all sorts of checks because it has the
167 if $destructor->is_needed;
171 my $memoized_methods = $self->options->{memoize};
172 foreach my $method_name (keys %{$memoized_methods}) {
173 my $type = $memoized_methods->{$method_name};
175 ($metaclass->can($method_name))
176 || confess "Could not find the method '$method_name' in " . $metaclass->name;
178 if ($type eq 'ARRAY') {
179 $metaclass->{'___' . $method_name} = [ $metaclass->$method_name ];
181 elsif ($type eq 'HASH') {
182 $metaclass->{'___' . $method_name} = { $metaclass->$method_name };
184 elsif ($type eq 'SCALAR') {
185 $metaclass->{'___' . $method_name} = $metaclass->$method_name;
189 $metaclass->{'___original_class'} = blessed($metaclass);
190 bless $metaclass => $self->immutable_metaclass->name;
193 sub make_metaclass_mutable {
194 my ($self, $immutable, $options) = @_;
196 my %options = %$options;
198 my $original_class = $immutable->get_mutable_metaclass_name;
199 delete $immutable->{'___original_class'} ;
200 bless $immutable => $original_class;
202 my $memoized_methods = $self->options->{memoize};
203 foreach my $method_name (keys %{$memoized_methods}) {
204 my $type = $memoized_methods->{$method_name};
206 ($immutable->can($method_name))
207 || confess "Could not find the method '$method_name' in " . $immutable->name;
208 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
209 delete $immutable->{'___' . $method_name};
213 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
214 $immutable->remove_method('DESTROY')
215 if blessed($immutable->get_method('DESTROY')) eq $options{destructor_class};
219 # 14:01 <@stevan> nah,. you shouldnt
220 # 14:01 <@stevan> they are just inlined
221 # 14:01 <@stevan> which is the default in Moose anyway
222 # 14:02 <@stevan> and adding new attributes will just DWIM
223 # 14:02 <@stevan> and you really cant change an attribute anyway
224 # if ($options{inline_accessors}) {
225 # foreach my $attr_name ($immutable->get_attribute_list) {
226 # my $attr = $immutable->get_attribute($attr_name);
227 # $attr->remove_accessors;
228 # $attr->install_accessors(0);
232 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
233 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
234 # 14:27 <@stevan> so I am not worried
235 if ($options{inline_constructor} && $immutable->has_method($options{constructor_name})) {
236 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
237 $immutable->remove_method( $options{constructor_name} )
238 if blessed($immutable->get_method($options{constructor_name})) eq $constructor_class;
242 sub create_methods_for_immutable_metaclass {
245 my %methods = %DEFAULT_METHODS;
246 my $metaclass = $self->metaclass;
247 my $meta = $metaclass->meta;
249 foreach my $read_only_method (@{$self->options->{read_only}}) {
250 my $method = $meta->find_method_by_name($read_only_method);
253 || confess "Could not find the method '$read_only_method' in " . $metaclass->name;
255 $methods{$read_only_method} = sub {
256 confess "This method is read-only" if scalar @_ > 1;
257 goto &{$method->body}
261 foreach my $cannot_call_method (@{$self->options->{cannot_call}}) {
262 $methods{$cannot_call_method} = sub {
263 confess "This method ($cannot_call_method) cannot be called on an immutable instance";
267 my $memoized_methods = $self->options->{memoize};
268 foreach my $method_name (keys %{$memoized_methods}) {
269 my $type = $memoized_methods->{$method_name};
270 if ($type eq 'ARRAY') {
271 $methods{$method_name} = sub { @{$_[0]->{'___' . $method_name}} };
273 elsif ($type eq 'HASH') {
274 $methods{$method_name} = sub { %{$_[0]->{'___' . $method_name}} };
276 elsif ($type eq 'SCALAR') {
277 $methods{$method_name} = sub { $_[0]->{'___' . $method_name} };
281 my $wrapped_methods = $self->options->{wrapped};
283 foreach my $method_name (keys %{ $wrapped_methods }) {
284 my $method = $meta->find_method_by_name($method_name);
287 || confess "Could not find the method '$method_name' in " . $metaclass->name;
289 my $wrapper = $wrapped_methods->{$method_name};
291 $methods{$method_name} = sub { $wrapper->($method, @_) };
294 $methods{get_mutable_metaclass_name} = sub { (shift)->{'___original_class'} };
296 $methods{immutable_transformer} = sub { $self };
309 Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
313 use Class::MOP::Immutable;
315 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
316 read_only => [qw/superclasses/],
324 remove_package_symbol
327 class_precedence_list => 'ARRAY',
328 compute_all_applicable_attributes => 'ARRAY',
329 get_meta_instance => 'SCALAR',
330 get_method_map => 'SCALAR',
334 $immutable_metaclass->make_metaclass_immutable(@_)
338 This is basically a module for applying a transformation on a given
339 metaclass. Current features include making methods read-only,
340 making methods un-callable and memoizing methods (in a type specific
343 This module is not for the feint of heart, it does some whacky things
344 to the metaclass in order to make it immutable. If you are just curious,
345 I suggest you turn back now, there is nothing to see here.
351 =item B<new ($metaclass, \%options)>
353 Given a C<$metaclass> and a set of C<%options> this module will
354 prepare an immutable version of the C<$metaclass>, which can then
355 be applied to the C<$metaclass> using the C<make_metaclass_immutable>
360 Returns the options HASH set in C<new>.
364 Returns the metaclass set in C<new>.
366 =item B<immutable_metaclass>
368 Returns the immutable metaclass created within C<new>.
374 =item B<create_immutable_metaclass>
376 This will create the immutable version of the C<$metaclass>, but will
377 not actually change the original metaclass.
379 =item B<create_methods_for_immutable_metaclass>
381 This will create all the methods for the immutable metaclass based
382 on the C<%options> passed into C<new>.
384 =item B<make_metaclass_immutable (%options)>
386 This will actually change the C<$metaclass> into the immutable version.
388 =item B<make_metaclass_mutable (%options)>
390 This will change the C<$metaclass> into the mutable version by reversing
391 the immutable process. C<%options> should be the same options that were
392 given to make_metaclass_immutable.
398 Stevan Little E<lt>stevan@iinteractive.comE<gt>
400 =head1 COPYRIGHT AND LICENSE
402 Copyright 2006-2008 by Infinity Interactive, Inc.
404 L<http://www.iinteractive.com>
406 This library is free software; you can redistribute it and/or modify
407 it under the same terms as Perl itself.