2 package Class::MOP::Immutable;
7 use Class::MOP::Method::Constructor;
10 use Scalar::Util 'blessed';
12 our $VERSION = '0.71_01';
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 $self->_inline_accessors( $metaclass, \%options );
120 $self->_inline_constructor( $metaclass, \%options );
121 $self->_inline_destructor( $metaclass, \%options );
122 $self->_memoize_methods( $metaclass, \%options );
124 $metaclass->{'___original_class'} = blessed($metaclass);
125 bless $metaclass => $self->immutable_metaclass->name;
128 sub _inline_accessors {
129 my ( $self, $metaclass, $options ) = @_;
131 return unless $options->{inline_accessors};
133 foreach my $attr_name ( $metaclass->get_attribute_list ) {
134 $metaclass->get_attribute($attr_name)->install_accessors(1);
138 sub _inline_constructor {
139 my ( $self, $metaclass, $options ) = @_;
141 return unless $options->{inline_constructor};
143 my $constructor_class = $options->{constructor_class}
144 || 'Class::MOP::Method::Constructor';
145 $metaclass->add_method(
146 $options->{constructor_name},
147 $constructor_class->new(
149 metaclass => $metaclass,
151 package_name => $metaclass->name,
152 name => $options->{constructor_name}
155 if $options->{replace_constructor}
156 or !$metaclass->has_method( $options->{constructor_name} );
159 sub _inline_destructor {
160 my ( $self, $metaclass, $options ) = @_;
162 return unless $options->{inline_destructor};
164 ( exists $options->{destructor_class} )
165 || confess "The 'inline_destructor' option is present, but "
166 . "no destructor class was specified";
168 my $destructor_class = $options->{destructor_class};
170 if ( $destructor_class->is_needed($metaclass) ) {
171 my $destructor = $destructor_class->new(
173 metaclass => $metaclass,
174 package_name => $metaclass->name,
178 $metaclass->add_method( 'DESTROY' => $destructor )
179 if $destructor->is_needed;
183 sub _memoize_methods {
184 my ( $self, $metaclass, $options ) = @_;
186 my $memoized_methods = $self->options->{memoize};
187 foreach my $method_name ( keys %{$memoized_methods} ) {
188 my $type = $memoized_methods->{$method_name};
190 ( $metaclass->can($method_name) )
191 || confess "Could not find the method '$method_name' in "
196 sub make_metaclass_mutable {
197 my ($self, $immutable, $options) = @_;
199 my %options = %$options;
201 my $original_class = $immutable->get_mutable_metaclass_name;
202 delete $immutable->{'___original_class'} ;
203 bless $immutable => $original_class;
205 my $memoized_methods = $self->options->{memoize};
206 foreach my $method_name (keys %{$memoized_methods}) {
207 my $type = $memoized_methods->{$method_name};
209 ($immutable->can($method_name))
210 || confess "Could not find the method '$method_name' in " . $immutable->name;
211 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
212 delete $immutable->{'___' . $method_name};
216 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
217 $immutable->remove_method('DESTROY')
218 if blessed($immutable->get_method('DESTROY')) eq $options{destructor_class};
222 # 14:01 <@stevan> nah,. you shouldnt
223 # 14:01 <@stevan> they are just inlined
224 # 14:01 <@stevan> which is the default in Moose anyway
225 # 14:02 <@stevan> and adding new attributes will just DWIM
226 # 14:02 <@stevan> and you really cant change an attribute anyway
227 # if ($options{inline_accessors}) {
228 # foreach my $attr_name ($immutable->get_attribute_list) {
229 # my $attr = $immutable->get_attribute($attr_name);
230 # $attr->remove_accessors;
231 # $attr->install_accessors(0);
235 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
236 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
237 # 14:27 <@stevan> so I am not worried
238 if ($options{inline_constructor} && $immutable->has_method($options{constructor_name})) {
239 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
240 $immutable->remove_method( $options{constructor_name} )
241 if blessed($immutable->get_method($options{constructor_name})) eq $constructor_class;
245 sub create_methods_for_immutable_metaclass {
248 my %methods = %DEFAULT_METHODS;
249 my $metaclass = $self->metaclass;
250 my $meta = $metaclass->meta;
252 foreach my $read_only_method (@{$self->options->{read_only}}) {
253 my $method = $meta->find_method_by_name($read_only_method);
256 || confess "Could not find the method '$read_only_method' in " . $metaclass->name;
258 $methods{$read_only_method} = sub {
259 confess "This method is read-only" if scalar @_ > 1;
260 goto &{$method->body}
264 foreach my $cannot_call_method (@{$self->options->{cannot_call}}) {
265 $methods{$cannot_call_method} = sub {
266 confess "This method ($cannot_call_method) cannot be called on an immutable instance";
270 my $memoized_methods = $self->options->{memoize};
271 foreach my $method_name (keys %{$memoized_methods}) {
272 my $type = $memoized_methods->{$method_name};
273 my $key = '___' . $method_name;
274 my $method = $meta->find_method_by_name($method_name);
276 if ($type eq 'ARRAY') {
277 $methods{$method_name} = sub {
278 @{$_[0]->{$key}} = $method->execute($_[0])
279 if !exists $_[0]->{$key};
280 return @{$_[0]->{$key}};
283 elsif ($type eq 'HASH') {
284 $methods{$method_name} = sub {
285 %{$_[0]->{$key}} = $method->execute($_[0])
286 if !exists $_[0]->{$key};
287 return %{$_[0]->{$key}};
290 elsif ($type eq 'SCALAR') {
291 $methods{$method_name} = sub {
292 $_[0]->{$key} = $method->execute($_[0])
293 if !exists $_[0]->{$key};
294 return $_[0]->{$key};
299 my $wrapped_methods = $self->options->{wrapped};
301 foreach my $method_name (keys %{ $wrapped_methods }) {
302 my $method = $meta->find_method_by_name($method_name);
305 || confess "Could not find the method '$method_name' in " . $metaclass->name;
307 my $wrapper = $wrapped_methods->{$method_name};
309 $methods{$method_name} = sub { $wrapper->($method, @_) };
312 $methods{get_mutable_metaclass_name} = sub { (shift)->{'___original_class'} };
314 $methods{immutable_transformer} = sub { $self };
327 Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
331 use Class::MOP::Immutable;
333 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
334 read_only => [qw/superclasses/],
342 remove_package_symbol
345 class_precedence_list => 'ARRAY',
346 compute_all_applicable_attributes => 'ARRAY',
347 get_meta_instance => 'SCALAR',
348 get_method_map => 'SCALAR',
352 $immutable_metaclass->make_metaclass_immutable(@_)
356 This is basically a module for applying a transformation on a given
357 metaclass. Current features include making methods read-only,
358 making methods un-callable and memoizing methods (in a type specific
361 This module is not for the feint of heart, it does some whacky things
362 to the metaclass in order to make it immutable. If you are just curious,
363 I suggest you turn back now, there is nothing to see here.
369 =item B<new ($metaclass, \%options)>
371 Given a C<$metaclass> and a set of C<%options> this module will
372 prepare an immutable version of the C<$metaclass>, which can then
373 be applied to the C<$metaclass> using the C<make_metaclass_immutable>
378 Returns the options HASH set in C<new>.
382 Returns the metaclass set in C<new>.
384 =item B<immutable_metaclass>
386 Returns the immutable metaclass created within C<new>.
392 =item B<create_immutable_metaclass>
394 This will create the immutable version of the C<$metaclass>, but will
395 not actually change the original metaclass.
397 =item B<create_methods_for_immutable_metaclass>
399 This will create all the methods for the immutable metaclass based
400 on the C<%options> passed into C<new>.
402 =item B<make_metaclass_immutable (%options)>
404 This will actually change the C<$metaclass> into the immutable version.
406 =item B<make_metaclass_mutable (%options)>
408 This will change the C<$metaclass> into the mutable version by reversing
409 the immutable process. C<%options> should be the same options that were
410 given to make_metaclass_immutable.
416 Stevan Little E<lt>stevan@iinteractive.comE<gt>
418 =head1 COPYRIGHT AND LICENSE
420 Copyright 2006-2008 by Infinity Interactive, Inc.
422 L<http://www.iinteractive.com>
424 This library is free software; you can redistribute it and/or modify
425 it under the same terms as Perl itself.