--- /dev/null
+
+package Class::MOP::Class::Immutable;
+
+use strict;
+use warnings;
+
+use Carp 'confess';
+use Scalar::Util 'blessed';
+
+our $VERSION = '0.01';
+
+use base 'Class::MOP::Class';
+
+# methods which can *not* be called
+
+sub reinitialize { confess 'Cannot call method "reinitialize" on an immutable instance' }
+
+sub add_method { confess 'Cannot call method "add_method" on an immutable instance' }
+sub alias_method { confess 'Cannot call method "alias_method" on an immutable instance' }
+sub remove_method { confess 'Cannot call method "remove_method" on an immutable instance' }
+
+sub add_attribute { confess 'Cannot call method "add_attribute" on an immutable instance' }
+sub remove_attribute { confess 'Cannot call method "remove_attribute" on an immutable instance' }
+
+sub add_package_variable { confess 'Cannot call method "add_package_variable" on an immutable instance' }
+sub remove_package_variable { confess 'Cannot call method "remove_package_variable" on an immutable instance' }
+
+# NOTE:
+# superclasses is an accessor, so
+# it just cannot be changed
+sub superclasses {
+ my $class = shift;
+ (!@_) || confess 'Cannot change the "superclasses" on an immmutable instance';
+ no strict 'refs';
+ @{$class->name . '::ISA'};
+}
+
+# predicates
+
+sub is_mutable { 0 }
+sub is_immutable { 1 }
+
+sub make_immutable { () }
+
+sub make_metaclass_immutable {
+ my ($class, $metaclass) = @_;
+ $metaclass->{'___class_precedence_list'} = [ $metaclass->class_precedence_list ];
+ $metaclass->{'___get_meta_instance'} = $metaclass->get_meta_instance;
+ $metaclass->{'___compute_all_applicable_attributes'} = [ $metaclass->compute_all_applicable_attributes ];
+ $metaclass->{'___original_class'} = blessed($metaclass);
+ bless $metaclass => $class;
+}
+
+# cached methods
+
+sub get_meta_instance { (shift)->{'___get_meta_instance'} }
+
+sub class_precedence_list {
+ @{ (shift)->{'___class_precedence_list'} }
+}
+
+sub compute_all_applicable_attributes {
+ @{ (shift)->{'___compute_all_applicable_attributes'} }
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Class::MOP::Class::Immutable - An immutable version of Class::MOP::Class
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+This will return a B<Class::MOP::Class> instance which is related
+to this class.
+
+=back
+
+=over 4
+
+
+=back
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2006 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 80;
+use Test::Exception;
+
+BEGIN {
+ use_ok('Class::MOP');
+ use_ok('Class::MOP::Class::Immutable');
+}
+
+{
+ package Foo;
+
+ use strict;
+ use warnings;
+ use metaclass;
+
+ __PACKAGE__->meta->add_attribute('bar');
+
+ package Bar;
+
+ use strict;
+ use warnings;
+ use metaclass;
+
+ __PACKAGE__->meta->superclasses('Foo');
+
+ __PACKAGE__->meta->add_attribute('baz');
+
+ package Baz;
+
+ use strict;
+ use warnings;
+ use metaclass;
+
+ __PACKAGE__->meta->superclasses('Bar');
+
+ __PACKAGE__->meta->add_attribute('bah');
+}
+
+{
+ my $meta = Foo->meta;
+ is($meta->name, 'Foo', '... checking the Foo metaclass');
+
+ ok($meta->is_mutable, '... our class is mutable');
+ ok(!$meta->is_immutable, '... our class is not immutable');
+
+ lives_ok {
+ $meta->make_immutable();
+ } '... changed Foo to be immutable';
+
+ ok(!$meta->make_immutable, '... make immutable now returns nothing');
+
+ ok(!$meta->is_mutable, '... our class is no longer mutable');
+ ok($meta->is_immutable, '... our class is now immutable');
+
+ isa_ok($meta, 'Class::MOP::Class::Immutable');
+ isa_ok($meta, 'Class::MOP::Class');
+
+ dies_ok { $meta->reinitialize() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_method() } '... exception thrown as expected';
+ dies_ok { $meta->alias_method() } '... exception thrown as expected';
+ dies_ok { $meta->remove_method() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_attribute() } '... exception thrown as expected';
+ dies_ok { $meta->remove_attribute() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_package_variable() } '... exception thrown as expected';
+ dies_ok { $meta->remove_package_variable() } '... exception thrown as expected';
+
+ my @supers;
+ lives_ok {
+ @supers = $meta->superclasses;
+ } '... got the superclasses okay';
+
+ dies_ok { $meta->superclasses([ 'UNIVERSAL' ]) } '... but could not set the superclasses okay';
+
+ my $meta_instance;
+ lives_ok {
+ $meta_instance = $meta->get_meta_instance;
+ } '... got the meta instance okay';
+ isa_ok($meta_instance, 'Class::MOP::Instance');
+ is($meta_instance, $meta->get_meta_instance, '... and we know it is cached');
+
+ my @cpl;
+ lives_ok {
+ @cpl = $meta->class_precedence_list;
+ } '... got the class precedence list okay';
+ is_deeply(
+ \@cpl,
+ [ 'Foo' ],
+ '... we just have ourselves in the class precedence list');
+
+ my @attributes;
+ lives_ok {
+ @attributes = $meta->compute_all_applicable_attributes;
+ } '... got the attribute list okay';
+ is_deeply(
+ \@attributes,
+ [ $meta->get_attribute('bar') ],
+ '... got the right list of attributes');
+}
+
+{
+ my $meta = Bar->meta;
+ is($meta->name, 'Bar', '... checking the Bar metaclass');
+
+ ok($meta->is_mutable, '... our class is mutable');
+ ok(!$meta->is_immutable, '... our class is not immutable');
+
+ lives_ok {
+ $meta->make_immutable();
+ } '... changed Bar to be immutable';
+
+ ok(!$meta->make_immutable, '... make immutable now returns nothing');
+
+ ok(!$meta->is_mutable, '... our class is no longer mutable');
+ ok($meta->is_immutable, '... our class is now immutable');
+
+ isa_ok($meta, 'Class::MOP::Class::Immutable');
+ isa_ok($meta, 'Class::MOP::Class');
+
+ dies_ok { $meta->reinitialize() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_method() } '... exception thrown as expected';
+ dies_ok { $meta->alias_method() } '... exception thrown as expected';
+ dies_ok { $meta->remove_method() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_attribute() } '... exception thrown as expected';
+ dies_ok { $meta->remove_attribute() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_package_variable() } '... exception thrown as expected';
+ dies_ok { $meta->remove_package_variable() } '... exception thrown as expected';
+
+ my @supers;
+ lives_ok {
+ @supers = $meta->superclasses;
+ } '... got the superclasses okay';
+
+ dies_ok { $meta->superclasses([ 'UNIVERSAL' ]) } '... but could not set the superclasses okay';
+
+ my $meta_instance;
+ lives_ok {
+ $meta_instance = $meta->get_meta_instance;
+ } '... got the meta instance okay';
+ isa_ok($meta_instance, 'Class::MOP::Instance');
+ is($meta_instance, $meta->get_meta_instance, '... and we know it is cached');
+
+ my @cpl;
+ lives_ok {
+ @cpl = $meta->class_precedence_list;
+ } '... got the class precedence list okay';
+ is_deeply(
+ \@cpl,
+ [ 'Bar', 'Foo'],
+ '... we just have ourselves in the class precedence list');
+
+ my @attributes;
+ lives_ok {
+ @attributes = $meta->compute_all_applicable_attributes;
+ } '... got the attribute list okay';
+ is_deeply(
+ [ sort { $a->name cmp $b->name } @attributes ],
+ [ Foo->meta->get_attribute('bar'), $meta->get_attribute('baz') ],
+ '... got the right list of attributes');
+}
+
+{
+ my $meta = Baz->meta;
+ is($meta->name, 'Baz', '... checking the Baz metaclass');
+
+ ok($meta->is_mutable, '... our class is mutable');
+ ok(!$meta->is_immutable, '... our class is not immutable');
+
+ lives_ok {
+ $meta->make_immutable();
+ } '... changed Baz to be immutable';
+
+ ok(!$meta->make_immutable, '... make immutable now returns nothing');
+
+ ok(!$meta->is_mutable, '... our class is no longer mutable');
+ ok($meta->is_immutable, '... our class is now immutable');
+
+ isa_ok($meta, 'Class::MOP::Class::Immutable');
+ isa_ok($meta, 'Class::MOP::Class');
+
+ dies_ok { $meta->reinitialize() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_method() } '... exception thrown as expected';
+ dies_ok { $meta->alias_method() } '... exception thrown as expected';
+ dies_ok { $meta->remove_method() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_attribute() } '... exception thrown as expected';
+ dies_ok { $meta->remove_attribute() } '... exception thrown as expected';
+
+ dies_ok { $meta->add_package_variable() } '... exception thrown as expected';
+ dies_ok { $meta->remove_package_variable() } '... exception thrown as expected';
+
+ my @supers;
+ lives_ok {
+ @supers = $meta->superclasses;
+ } '... got the superclasses okay';
+
+ dies_ok { $meta->superclasses([ 'UNIVERSAL' ]) } '... but could not set the superclasses okay';
+
+ my $meta_instance;
+ lives_ok {
+ $meta_instance = $meta->get_meta_instance;
+ } '... got the meta instance okay';
+ isa_ok($meta_instance, 'Class::MOP::Instance');
+ is($meta_instance, $meta->get_meta_instance, '... and we know it is cached');
+
+ my @cpl;
+ lives_ok {
+ @cpl = $meta->class_precedence_list;
+ } '... got the class precedence list okay';
+ is_deeply(
+ \@cpl,
+ [ 'Baz', 'Bar', 'Foo'],
+ '... we just have ourselves in the class precedence list');
+
+ my @attributes;
+ lives_ok {
+ @attributes = $meta->compute_all_applicable_attributes;
+ } '... got the attribute list okay';
+ is_deeply(
+ [ sort { $a->name cmp $b->name } @attributes ],
+ [ $meta->get_attribute('bah'), Foo->meta->get_attribute('bar'), Bar->meta->get_attribute('baz') ],
+ '... got the right list of attributes');
+}
+
+