2 package Class::MOP::Immutable;
7 use Class::MOP::Method::Constructor;
10 use Scalar::Util 'blessed';
12 our $VERSION = '0.02';
13 our $AUTHORITY = 'cpan:STEVAN';
16 my ($class, $metaclass, $options) = @_;
19 '$!metaclass' => $metaclass,
20 '%!options' => $options,
21 '$!immutable_metaclass' => undef,
25 # we initialize the immutable
26 # version of the metaclass here
27 $self->create_immutable_metaclass;
32 sub immutable_metaclass { (shift)->{'$!immutable_metaclass'} }
33 sub metaclass { (shift)->{'$!metaclass'} }
34 sub options { (shift)->{'%!options'} }
36 sub create_immutable_metaclass {
40 # The immutable version of the
41 # metaclass is just a anon-class
42 # which shadows the methods
44 $self->{'$!immutable_metaclass'} = Class::MOP::Class->create_anon_class(
45 superclasses => [ blessed($self->metaclass) ],
46 methods => $self->create_methods_for_immutable_metaclass,
51 my %DEFAULT_METHODS = (
52 # I don't really understand this, but removing it breaks tests (groditi)
55 # if it is not blessed, then someone is asking
56 # for the meta of Class::MOP::Class::Immutable
57 return Class::MOP::Class->initialize($self) unless blessed($self);
58 # otherwise, they are asking for the metaclass
59 # which has been made immutable, which is itself
62 is_mutable => sub { 0 },
63 is_immutable => sub { 1 },
64 make_immutable => sub { () },
68 # this will actually convert the
69 # existing metaclass to an immutable
71 sub make_metaclass_immutable {
72 my ($self, $metaclass, %options) = @_;
74 $options{inline_accessors} = 1 unless exists $options{inline_accessors};
75 $options{inline_constructor} = 1 unless exists $options{inline_constructor};
76 $options{inline_destructor} = 0 unless exists $options{inline_destructor};
77 $options{constructor_name} = 'new' unless exists $options{constructor_name};
78 $options{debug} = 0 unless exists $options{debug};
80 if ($options{inline_accessors}) {
81 foreach my $attr_name ($metaclass->get_attribute_list) {
82 # inline the accessors
83 $metaclass->get_attribute($attr_name)
84 ->install_accessors(1);
88 if ($options{inline_constructor}) {
89 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
90 $metaclass->add_method(
91 $options{constructor_name},
92 $constructor_class->new(
94 metaclass => $metaclass,
97 ) unless $metaclass->has_method($options{constructor_name});
100 if ($options{inline_destructor}) {
101 (exists $options{destructor_class})
102 || confess "The 'inline_destructor' option is present, but "
103 . "no destructor class was specified";
105 my $destructor_class = $options{destructor_class};
107 my $destructor = $destructor_class->new(
108 options => \%options,
109 metaclass => $metaclass,
112 $metaclass->add_method('DESTROY' => $destructor)
114 # we allow the destructor to determine
115 # if it is needed or not, it can perform
116 # all sorts of checks because it has the
118 if $destructor->is_needed;
121 my $memoized_methods = $self->options->{memoize};
122 foreach my $method_name (keys %{$memoized_methods}) {
123 my $type = $memoized_methods->{$method_name};
125 ($metaclass->can($method_name))
126 || confess "Could not find the method '$method_name' in " . $metaclass->name;
128 if ($type eq 'ARRAY') {
129 $metaclass->{'___' . $method_name} = [ $metaclass->$method_name ];
131 elsif ($type eq 'HASH') {
132 $metaclass->{'___' . $method_name} = { $metaclass->$method_name };
134 elsif ($type eq 'SCALAR') {
135 $metaclass->{'___' . $method_name} = $metaclass->$method_name;
139 $metaclass->{'___original_class'} = blessed($metaclass);
140 bless $metaclass => $self->immutable_metaclass->name;
143 sub make_metaclass_mutable {
144 my ($self, $immutable, %options) = @_;
146 my $original_class = $immutable->get_mutable_metaclass_name;
147 delete $immutable->{'___original_class'} ;
148 bless $immutable => $original_class;
150 my $memoized_methods = $self->options->{memoize};
151 foreach my $method_name (keys %{$memoized_methods}) {
152 my $type = $memoized_methods->{$method_name};
154 ($immutable->can($method_name))
155 || confess "Could not find the method '$method_name' in " . $immutable->name;
156 if ($type eq 'SCALAR' || $type eq 'ARRAY' || $type eq 'HASH' ) {
157 delete $immutable->{'___' . $method_name};
161 if ($options{inline_destructor} && $immutable->has_method('DESTROY')) {
162 $immutable->remove_method('DESTROY')
163 if $immutable->get_method('DESTROY')->blessed eq $options{destructor_class};
167 # 14:01 <@stevan> nah,. you shouldnt
168 # 14:01 <@stevan> they are just inlined
169 # 14:01 <@stevan> which is the default in Moose anyway
170 # 14:02 <@stevan> and adding new attributes will just DWIM
171 # 14:02 <@stevan> and you really cant change an attribute anyway
172 # if ($options{inline_accessors}) {
173 # foreach my $attr_name ($immutable->get_attribute_list) {
174 # my $attr = $immutable->get_attribute($attr_name);
175 # $attr->remove_accessors;
176 # $attr->install_accessors(0);
180 # 14:26 <@stevan> the only user of ::Method::Constructor is immutable
181 # 14:27 <@stevan> if someone uses it outside of immutable,.. they are either: mst or groditi
182 # 14:27 <@stevan> so I am not worried
183 $options{constructor_name} = 'new' unless exists $options{constructor_name};
184 if ($options{inline_constructor}) {
185 my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor';
186 $immutable->remove_method( $options{constructor_name} )
187 if $immutable->get_method($options{constructor_name})->blessed eq $constructor_class;
191 sub create_methods_for_immutable_metaclass {
194 my %methods = %DEFAULT_METHODS;
196 foreach my $read_only_method (@{$self->options->{read_only}}) {
197 my $method = $self->metaclass->meta->find_method_by_name($read_only_method);
200 || confess "Could not find the method '$read_only_method' in " . $self->metaclass->name;
202 $methods{$read_only_method} = sub {
203 confess "This method is read-only" if scalar @_ > 1;
204 goto &{$method->body}
208 foreach my $cannot_call_method (@{$self->options->{cannot_call}}) {
209 $methods{$cannot_call_method} = sub {
210 confess "This method ($cannot_call_method) cannot be called on an immutable instance";
214 my $memoized_methods = $self->options->{memoize};
215 foreach my $method_name (keys %{$memoized_methods}) {
216 my $type = $memoized_methods->{$method_name};
217 if ($type eq 'ARRAY') {
218 $methods{$method_name} = sub { @{$_[0]->{'___' . $method_name}} };
220 elsif ($type eq 'HASH') {
221 $methods{$method_name} = sub { %{$_[0]->{'___' . $method_name}} };
223 elsif ($type eq 'SCALAR') {
224 $methods{$method_name} = sub { $_[0]->{'___' . $method_name} };
228 $methods{get_mutable_metaclass_name} = sub { (shift)->{'___original_class'} };
241 Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses
245 use Class::MOP::Immutable;
247 my $immutable_metaclass = Class::MOP::Immutable->new($metaclass, {
248 read_only => [qw/superclasses/],
256 remove_package_symbol
259 class_precedence_list => 'ARRAY',
260 compute_all_applicable_attributes => 'ARRAY',
261 get_meta_instance => 'SCALAR',
262 get_method_map => 'SCALAR',
266 $immutable_metaclass->make_metaclass_immutable(@_)
270 This is basically a module for applying a transformation on a given
271 metaclass. Current features include making methods read-only,
272 making methods un-callable and memoizing methods (in a type specific
275 This module is fairly new to the MOP, and quite possibly will be
276 expanded and further generalized as the need arises.
282 =item B<new ($metaclass, \%options)>
284 Given a C<$metaclass> and a set of C<%options> this module will
285 prepare an immutable version of the C<$metaclass>, which can then
286 be applied to the C<$metaclass> using the C<make_metaclass_immutable>
291 Returns the options HASH set in C<new>.
295 Returns the metaclass set in C<new>.
297 =item B<immutable_metaclass>
299 Returns the immutable metaclass created within C<new>.
305 =item B<create_immutable_metaclass>
307 This will create the immutable version of the C<$metaclass>, but will
308 not actually change the original metaclass.
310 =item B<create_methods_for_immutable_metaclass>
312 This will create all the methods for the immutable metaclass based
313 on the C<%options> passed into C<new>.
315 =item B<make_metaclass_immutable (%options)>
317 This will actually change the C<$metaclass> into the immutable version.
319 =item B<make_metaclass_mutable (%options)>
321 This will change the C<$metaclass> into the mutable version by reversing
322 the immutable process. C<%options> should be the same options that were
323 given to make_metaclass_immutable.
329 Stevan Little E<lt>stevan@iinteractive.comE<gt>
331 =head1 COPYRIGHT AND LICENSE
333 Copyright 2006, 2007 by Infinity Interactive, Inc.
335 L<http://www.iinteractive.com>
337 This library is free software; you can redistribute it and/or modify
338 it under the same terms as Perl itself.