some additional tests for better coverage
[gitmo/Moose.git] / lib / Moose / Meta / Class.pm
CommitLineData
c0e30cf5 1
2package Moose::Meta::Class;
3
4use strict;
5use warnings;
6
648e79ae 7use Class::MOP;
8
6ba6d68c 9use Carp 'confess';
54b1cdf0 10use Scalar::Util 'weaken', 'blessed', 'reftype';
a15dff8d 11
238b424d 12our $VERSION = '0.19';
d44714be 13our $AUTHORITY = 'cpan:STEVAN';
bc1e29b5 14
8ee73eeb 15use Moose::Meta::Method::Overriden;
16
c0e30cf5 17use base 'Class::MOP::Class';
18
598340d5 19__PACKAGE__->meta->add_attribute('roles' => (
ef333f17 20 reader => 'roles',
21 default => sub { [] }
22));
23
590868a3 24sub initialize {
25 my $class = shift;
26 my $pkg = shift;
27 $class->SUPER::initialize($pkg,
ac2dc464 28 'attribute_metaclass' => 'Moose::Meta::Attribute',
5cf3dbcf 29 'method_metaclass' => 'Moose::Meta::Method',
ac2dc464 30 'instance_metaclass' => 'Moose::Meta::Instance',
590868a3 31 @_);
ac2dc464 32}
590868a3 33
ef333f17 34sub add_role {
35 my ($self, $role) = @_;
36 (blessed($role) && $role->isa('Moose::Meta::Role'))
37 || confess "Roles must be instances of Moose::Meta::Role";
38 push @{$self->roles} => $role;
39}
40
b8aeb4dc 41sub calculate_all_roles {
42 my $self = shift;
43 my %seen;
44 grep { !$seen{$_->name}++ } map { $_->calculate_all_roles } @{ $self->roles };
45}
46
ef333f17 47sub does_role {
48 my ($self, $role_name) = @_;
49 (defined $role_name)
50 || confess "You must supply a role name to look for";
9c429218 51 foreach my $class ($self->class_precedence_list) {
ac2dc464 52 next unless $class->can('meta');
9c429218 53 foreach my $role (@{$class->meta->roles}) {
54 return 1 if $role->does_role($role_name);
55 }
ef333f17 56 }
57 return 0;
58}
59
d79e62fd 60sub excludes_role {
61 my ($self, $role_name) = @_;
62 (defined $role_name)
63 || confess "You must supply a role name to look for";
ac2dc464 64 foreach my $class ($self->class_precedence_list) {
65 next unless $class->can('meta');
5cb193ed 66 # NOTE:
67 # in the pretty rare instance when a Moose metaclass
ac2dc464 68 # is itself extended with a role, this check needs to
5cb193ed 69 # be done since some items in the class_precedence_list
ac2dc464 70 # might in fact be Class::MOP based still.
71 next unless $class->meta->can('roles');
9c429218 72 foreach my $role (@{$class->meta->roles}) {
73 return 1 if $role->excludes_role($role_name);
74 }
d79e62fd 75 }
76 return 0;
77}
78
8c9d74e7 79sub new_object {
80 my ($class, %params) = @_;
81 my $self = $class->SUPER::new_object(%params);
82 foreach my $attr ($class->compute_all_applicable_attributes()) {
715ea0b7 83 # FIXME:
84 # this does not accept undefined
ac2dc464 85 # values, nor does it accept false
715ea0b7 86 # values to be passed into the init-arg
5faf11bb 87 next unless $params{$attr->init_arg} && $attr->can('has_trigger') && $attr->has_trigger;
88 $attr->trigger->($self, $params{$attr->init_arg}, $attr);
8c9d74e7 89 }
ac2dc464 90 return $self;
8c9d74e7 91}
92
a15dff8d 93sub construct_instance {
94 my ($class, %params) = @_;
ddd0ec20 95 my $meta_instance = $class->get_meta_instance;
575db57d 96 # FIXME:
97 # the code below is almost certainly incorrect
98 # but this is foreign inheritence, so we might
ac2dc464 99 # have to kludge it in the end.
ddd0ec20 100 my $instance = $params{'__INSTANCE__'} || $meta_instance->create_instance();
ac2dc464 101 foreach my $attr ($class->compute_all_applicable_attributes()) {
ddd0ec20 102 $attr->initialize_instance_slot($meta_instance, $instance, \%params)
a15dff8d 103 }
104 return $instance;
105}
106
093b12c2 107# FIXME:
108# This is ugly
ac2dc464 109sub get_method_map {
093b12c2 110 my $self = shift;
53dd42d8 111
112 if (defined $self->{'$!_package_cache_flag'} &&
66e08a8a 113 $self->{'$!_package_cache_flag'} == Class::MOP::check_package_cache_flag($self->meta->name)) {
53dd42d8 114 return $self->{'%!methods'};
115 }
116
ac2dc464 117 my $map = $self->{'%!methods'};
118
093b12c2 119 my $class_name = $self->name;
120 my $method_metaclass = $self->method_metaclass;
ac2dc464 121
093b12c2 122 foreach my $symbol ($self->list_all_package_symbols('CODE')) {
ac2dc464 123
093b12c2 124 my $code = $self->get_package_symbol('&' . $symbol);
ac2dc464 125
126 next if exists $map->{$symbol} &&
127 defined $map->{$symbol} &&
128 $map->{$symbol}->body == $code;
129
53dd42d8 130 my ($pkg, $name) = Class::MOP::get_code_info($code);
ac2dc464 131
53dd42d8 132 if ($pkg->can('meta')
4f8f3aab 133 # NOTE:
134 # we don't know what ->meta we are calling
53dd42d8 135 # here, so we need to be careful cause it
136 # just might blow up at us, or just complain
137 # loudly (in the case of Curses.pm) so we
4f8f3aab 138 # just be a little overly cautious here.
139 # - SL
140 && eval { no warnings; blessed($pkg->meta) }
141 && $pkg->meta->isa('Moose::Meta::Role')) {
093b12c2 142 #my $role = $pkg->meta->name;
143 #next unless $self->does_role($role);
144 }
145 else {
53dd42d8 146 next if ($pkg || '') ne $class_name &&
147 ($name || '') ne '__ANON__';
148
093b12c2 149 }
ac2dc464 150
093b12c2 151 $map->{$symbol} = $method_metaclass->wrap($code);
152 }
ac2dc464 153
093b12c2 154 return $map;
a7d0cd00 155}
156
093b12c2 157### ---------------------------------------------
158
a2eec5e7 159sub add_attribute {
160 my $self = shift;
d9bb6c63 161 $self->SUPER::add_attribute($self->_process_attribute(@_));
a2eec5e7 162}
163
78cd1d3b 164sub add_override_method_modifier {
165 my ($self, $name, $method, $_super_package) = @_;
d05cd563 166 (!$self->has_method($name))
167 || confess "Cannot add an override method if a local method is already present";
78cd1d3b 168 # need this for roles ...
169 $_super_package ||= $self->name;
170 my $super = $self->find_next_method_by_name($name);
171 (defined $super)
ac2dc464 172 || confess "You cannot override '$name' because it has no super method";
093b12c2 173 $self->add_method($name => Moose::Meta::Method::Overriden->wrap(sub {
78cd1d3b 174 my @args = @_;
78cd1d3b 175 no warnings 'redefine';
52c7c330 176 if ($Moose::SUPER_SLOT{$_super_package}) {
b644e331 177 local *{$Moose::SUPER_SLOT{$_super_package}} = sub { $super->body->(@args) };
b468a3d3 178 return $method->(@args);
52c7c330 179 } else {
b468a3d3 180 confess "Trying to call override modifier'd method without super()";
52c7c330 181 }
093b12c2 182 }));
78cd1d3b 183}
184
185sub add_augment_method_modifier {
ac2dc464 186 my ($self, $name, $method) = @_;
d05cd563 187 (!$self->has_method($name))
ac2dc464 188 || confess "Cannot add an augment method if a local method is already present";
78cd1d3b 189 my $super = $self->find_next_method_by_name($name);
190 (defined $super)
ac2dc464 191 || confess "You cannot augment '$name' because it has no super method";
192 my $_super_package = $super->package_name;
193 # BUT!,... if this is an overriden method ....
05d9eaf6 194 if ($super->isa('Moose::Meta::Method::Overriden')) {
ac2dc464 195 # we need to be sure that we actually
196 # find the next method, which is not
05d9eaf6 197 # an 'override' method, the reason is
ac2dc464 198 # that an 'override' method will not
05d9eaf6 199 # be the one calling inner()
ac2dc464 200 my $real_super = $self->_find_next_method_by_name_which_is_not_overridden($name);
05d9eaf6 201 $_super_package = $real_super->package_name;
ac2dc464 202 }
78cd1d3b 203 $self->add_method($name => sub {
204 my @args = @_;
78cd1d3b 205 no warnings 'redefine';
52c7c330 206 if ($Moose::INNER_SLOT{$_super_package}) {
53dd42d8 207 local *{$Moose::INNER_SLOT{$_super_package}} = sub {
208 local *{$Moose::INNER_SLOT{$_super_package}} = sub {};
b468a3d3 209 $method->(@args);
210 };
b644e331 211 return $super->body->(@args);
53dd42d8 212 }
213 else {
b644e331 214 return $super->body->(@args);
52c7c330 215 }
ac2dc464 216 });
78cd1d3b 217}
218
1341f10c 219## Private Utility methods ...
220
05d9eaf6 221sub _find_next_method_by_name_which_is_not_overridden {
222 my ($self, $name) = @_;
68efb014 223 foreach my $method ($self->find_all_methods_by_name($name)) {
ac2dc464 224 return $method->{code}
05d9eaf6 225 if blessed($method->{code}) && !$method->{code}->isa('Moose::Meta::Method::Overriden');
226 }
227 return undef;
228}
229
1341f10c 230sub _fix_metaclass_incompatability {
231 my ($self, @superclasses) = @_;
232 foreach my $super (@superclasses) {
233 # don't bother if it does not have a meta.
234 next unless $super->can('meta');
ac2dc464 235 # get the name, make sure we take
8ecb1fa0 236 # immutable classes into account
ac2dc464 237 my $super_meta_name = ($super->meta->is_immutable
8ecb1fa0 238 ? $super->meta->get_mutable_metaclass_name
239 : blessed($super->meta));
ac2dc464 240 # if it's meta is a vanilla Moose,
241 # then we can safely ignore it.
8ecb1fa0 242 next if $super_meta_name eq 'Moose::Meta::Class';
ac2dc464 243 # but if we have anything else,
1341f10c 244 # we need to check it out ...
245 unless (# see if of our metaclass is incompatible
8ecb1fa0 246 ($self->isa($super_meta_name) &&
1341f10c 247 # and see if our instance metaclass is incompatible
248 $self->instance_metaclass->isa($super->meta->instance_metaclass)) &&
249 # ... and if we are just a vanilla Moose
250 $self->isa('Moose::Meta::Class')) {
251 # re-initialize the meta ...
252 my $super_meta = $super->meta;
253 # NOTE:
ac2dc464 254 # We might want to consider actually
255 # transfering any attributes from the
256 # original meta into this one, but in
1341f10c 257 # general you should not have any there
ac2dc464 258 # at this point anyway, so it's very
1341f10c 259 # much an obscure edge case anyway
260 $self = $super_meta->reinitialize($self->name => (
ac2dc464 261 'attribute_metaclass' => $super_meta->attribute_metaclass,
5cf3dbcf 262 'method_metaclass' => $super_meta->method_metaclass,
263 'instance_metaclass' => $super_meta->instance_metaclass,
1341f10c 264 ));
265 }
266 }
ac2dc464 267 return $self;
1341f10c 268}
269
d7d8a8c7 270# NOTE:
d9bb6c63 271# this was crap anyway, see
272# Moose::Util::apply_all_roles
d7d8a8c7 273# instead
4498537c 274sub _apply_all_roles {
275 die 'DEPRECATED: use Moose::Util::apply_all_roles($meta, @roles) instead'
276}
1341f10c 277
d9bb6c63 278my %ANON_CLASSES;
279
1341f10c 280sub _process_attribute {
238b424d 281 my $self = shift;
282
283 return $_[0] if blessed $_[0] && $_[0]->isa('Class::MOP::Attribute');
284
d9bb6c63 285 my $name = shift;
286 my %options = ((scalar @_ == 1 && ref($_[0]) eq 'HASH') ? %{$_[0]} : @_);
287
1341f10c 288 if ($name =~ /^\+(.*)/) {
d7d8a8c7 289 return $self->_process_inherited_attribute($1, %options);
1341f10c 290 }
291 else {
d9bb6c63 292 my $attr_metaclass_name;
1341f10c 293 if ($options{metaclass}) {
c1935ade 294 my $metaclass_name = $options{metaclass};
295 eval {
296 my $possible_full_name = 'Moose::Meta::Attribute::Custom::' . $metaclass_name;
ac2dc464 297 Class::MOP::load_class($possible_full_name);
298 $metaclass_name = $possible_full_name->can('register_implementation')
c1935ade 299 ? $possible_full_name->register_implementation
300 : $possible_full_name;
301 };
302 if ($@) {
303 Class::MOP::load_class($metaclass_name);
304 }
d9bb6c63 305 $attr_metaclass_name = $metaclass_name;
1341f10c 306 }
307 else {
d9bb6c63 308 $attr_metaclass_name = $self->attribute_metaclass;
1341f10c 309 }
d9bb6c63 310
311 if ($options{traits}) {
312
313 my $anon_role_key = join "|" => @{$options{traits}};
314
315 my $class;
316 if (exists $ANON_CLASSES{$anon_role_key} && defined $ANON_CLASSES{$anon_role_key}) {
317 $class = $ANON_CLASSES{$anon_role_key};
318 }
319 else {
320 $class = Moose::Meta::Class->create_anon_class(
321 superclasses => [ $attr_metaclass_name ]
322 );
323 $ANON_CLASSES{$anon_role_key} = $class;
3bb22459 324
325 my @traits;
326 foreach my $trait (@{$options{traits}}) {
327 eval {
328 my $possible_full_name = 'Moose::Meta::Attribute::Custom::Trait::' . $trait;
329 Class::MOP::load_class($possible_full_name);
330 push @traits => $possible_full_name->can('register_implementation')
331 ? $possible_full_name->register_implementation
332 : $possible_full_name;
333 };
334 if ($@) {
335 push @traits => $trait;
336 }
337 }
338
339 Moose::Util::apply_all_roles($class, @traits);
d9bb6c63 340 }
341
342 $attr_metaclass_name = $class->name;
343 }
344
345 return $attr_metaclass_name->new($name, %options);
ac2dc464 346 }
1341f10c 347}
348
349sub _process_inherited_attribute {
350 my ($self, $attr_name, %options) = @_;
351 my $inherited_attr = $self->find_attribute_by_name($attr_name);
352 (defined $inherited_attr)
353 || confess "Could not find an attribute by the name of '$attr_name' to inherit from";
1341f10c 354 if ($inherited_attr->isa('Moose::Meta::Attribute')) {
d7d8a8c7 355 return $inherited_attr->clone_and_inherit_options(%options);
1341f10c 356 }
357 else {
358 # NOTE:
359 # kind of a kludge to handle Class::MOP::Attributes
d7d8a8c7 360 return $inherited_attr->Moose::Meta::Attribute::clone_and_inherit_options(%options);
ac2dc464 361 }
1341f10c 362}
363
5cf3dbcf 364## -------------------------------------------------
365
366use Moose::Meta::Method::Constructor;
1f779926 367use Moose::Meta::Method::Destructor;
5cf3dbcf 368
ac2dc464 369# This could be done by using SUPER and altering ->options
370# I am keeping it this way to make it more explicit.
371sub create_immutable_transformer {
372 my $self = shift;
373 my $class = Class::MOP::Immutable->new($self, {
374 read_only => [qw/superclasses/],
375 cannot_call => [qw/
376 add_method
377 alias_method
378 remove_method
379 add_attribute
380 remove_attribute
381 add_package_symbol
382 remove_package_symbol
383 add_role
384 /],
385 memoize => {
386 class_precedence_list => 'ARRAY',
387 compute_all_applicable_attributes => 'ARRAY',
388 get_meta_instance => 'SCALAR',
389 get_method_map => 'SCALAR',
390 # maybe ....
391 calculate_all_roles => 'ARRAY',
392 }
393 });
394 return $class;
395}
396
397sub make_immutable {
398 my $self = shift;
399 $self->SUPER::make_immutable
400 (
401 constructor_class => 'Moose::Meta::Method::Constructor',
402 destructor_class => 'Moose::Meta::Method::Destructor',
403 inline_destructor => 1,
404 # NOTE:
405 # no need to do this,
406 # Moose always does it
407 inline_accessors => 0,
408 @_,
409 );
5cf3dbcf 410}
411
c0e30cf5 4121;
413
414__END__
415
416=pod
417
418=head1 NAME
419
e522431d 420Moose::Meta::Class - The Moose metaclass
c0e30cf5 421
c0e30cf5 422=head1 DESCRIPTION
423
ac2dc464 424This is a subclass of L<Class::MOP::Class> with Moose specific
e522431d 425extensions.
426
ac2dc464 427For the most part, the only time you will ever encounter an
428instance of this class is if you are doing some serious deep
429introspection. To really understand this class, you need to refer
6ba6d68c 430to the L<Class::MOP::Class> documentation.
431
c0e30cf5 432=head1 METHODS
433
434=over 4
435
590868a3 436=item B<initialize>
437
5cf3dbcf 438=item B<make_immutable>
439
ac2dc464 440Override original to add default options for inlining destructor
441and altering the Constructor metaclass.
442
443=item B<create_immutable_transformer>
444
445Override original to lock C<add_role> and memoize C<calculate_all_roles>
446
8c9d74e7 447=item B<new_object>
448
02a0fb52 449We override this method to support the C<trigger> attribute option.
450
a15dff8d 451=item B<construct_instance>
452
ac2dc464 453This provides some Moose specific extensions to this method, you
454almost never call this method directly unless you really know what
455you are doing.
6ba6d68c 456
457This method makes sure to handle the moose weak-ref, type-constraint
ac2dc464 458and type coercion features.
ef1d5f4b 459
093b12c2 460=item B<get_method_map>
e9ec68d6 461
ac2dc464 462This accommodates Moose::Meta::Role::Method instances, which are
463aliased, instead of added, but still need to be counted as valid
e9ec68d6 464methods.
465
78cd1d3b 466=item B<add_override_method_modifier ($name, $method)>
467
ac2dc464 468This will create an C<override> method modifier for you, and install
02a0fb52 469it in the package.
470
78cd1d3b 471=item B<add_augment_method_modifier ($name, $method)>
472
ac2dc464 473This will create an C<augment> method modifier for you, and install
02a0fb52 474it in the package.
475
2b14ac61 476=item B<calculate_all_roles>
477
ef333f17 478=item B<roles>
479
ac2dc464 480This will return an array of C<Moose::Meta::Role> instances which are
02a0fb52 481attached to this class.
482
ef333f17 483=item B<add_role ($role)>
484
ac2dc464 485This takes an instance of C<Moose::Meta::Role> in C<$role>, and adds it
02a0fb52 486to the list of associated roles.
487
ef333f17 488=item B<does_role ($role_name)>
489
ac2dc464 490This will test if this class C<does> a given C<$role_name>. It will
491not only check it's local roles, but ask them as well in order to
02a0fb52 492cascade down the role hierarchy.
493
d79e62fd 494=item B<excludes_role ($role_name)>
495
ac2dc464 496This will test if this class C<excludes> a given C<$role_name>. It will
497not only check it's local roles, but ask them as well in order to
d79e62fd 498cascade down the role hierarchy.
499
9e93dd19 500=item B<add_attribute ($attr_name, %params|$params)>
4e848edb 501
9e93dd19 502This method does the same thing as L<Class::MOP::Class::add_attribute>, but adds
503support for taking the C<$params> as a HASH ref.
ac1ef2f9 504
c0e30cf5 505=back
506
507=head1 BUGS
508
ac2dc464 509All complex software has bugs lurking in it, and this module is no
c0e30cf5 510exception. If you find a bug please either email me, or add the bug
511to cpan-RT.
512
c0e30cf5 513=head1 AUTHOR
514
515Stevan Little E<lt>stevan@iinteractive.comE<gt>
516
517=head1 COPYRIGHT AND LICENSE
518
778db3ac 519Copyright 2006-2008 by Infinity Interactive, Inc.
c0e30cf5 520
521L<http://www.iinteractive.com>
522
523This library is free software; you can redistribute it and/or modify
ac2dc464 524it under the same terms as Perl itself.
c0e30cf5 525
8a7a9c53 526=cut
1a563243 527