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