Refactor many many things
[gitmo/Mouse.git] / lib / Mouse / Meta / Attribute.pm
1 package Mouse::Meta::Attribute;
2 use strict;
3 use warnings;
4
5 use Carp ();
6 use Scalar::Util qw(weaken);
7
8 use Mouse::Util;
9
10 use Mouse::Meta::TypeConstraint;
11 use Mouse::Meta::Method::Accessor;
12
13 sub BUILDARGS{
14     my $class = shift;
15     my $name  = shift;
16     my %args  = (@_ == 1) ? %{$_[0]} : @_;
17
18     $args{name} = $name;
19
20     # XXX: for backward compatibility (with method modifiers)
21     if($class->can('canonicalize_args') != \&canonicalize_args){
22         %args = $class->canonicalize_args($name, %args);
23     }
24
25     return \%args;
26 }
27
28 sub new {
29     my $class = shift;
30     my $args  = $class->BUILDARGS(@_);
31
32     my $name = $args->{name};
33
34     # taken from Class::MOP::Attribute::new
35
36     defined($name)
37         or $class->throw_error('You must provide a name for the attribute');
38
39     if(!exists $args->{init_arg}){
40         $args->{init_arg} = $name;
41     }
42
43     # 'required' requires eigher 'init_arg', 'builder', or 'default'
44     my $can_be_required = defined( $args->{init_arg} );
45
46     if(exists $args->{builder}){
47         $class->throw_error('builder must be a defined scalar value which is a method name')
48             if ref $args->{builder} || !(defined $args->{builder});
49
50         $can_be_required++;
51     }
52     elsif(exists $args->{default}){
53         if(ref $args->{default} && ref($args->{default}) ne 'CODE'){
54             $class->throw_error("References are not allowed as default values, you must "
55                               . "wrap the default of '$name' in a CODE reference (ex: sub { [] } and not [])");
56         }
57         $can_be_required++;
58     }
59
60     if( $args->{required} && !$can_be_required ) {
61         $class->throw_error("You cannot have a required attribute ($name) without a default, builder, or an init_arg");
62     }
63
64     # taken from Mouse::Meta::Attribute->new and _process_args->
65
66     if(exists $args->{is}){
67         my $is = $args->{is};
68
69         if($is eq 'ro'){
70             $args->{reader} ||= $name;
71         }
72         elsif($is eq 'rw'){
73             if(exists $args->{writer}){
74                 $args->{reader} ||= $name;
75              }
76              else{
77                 $args->{accessor} ||= $name;
78              }
79         }
80         elsif($is eq 'bare'){
81             # do nothing, but don't complain (later) about missing methods
82         }
83         else{
84             $is = 'undef' if !defined $is;
85             $class->throw_error("I do not understand this option (is => $is) on attribute ($name)");
86         }
87     }
88
89     my $tc;
90     if(exists $args->{isa}){
91         $args->{type_constraint} = Mouse::Util::TypeConstraints::find_or_create_isa_type_constraint($args->{isa});
92     }
93     elsif(exists $args->{does}){
94         $args->{type_constraint} = Mouse::Util::TypeConstraints::find_or_create_does_type_constraint($args->{does});
95     }
96     $tc = $args->{type_constraint};
97
98     if($args->{coerce}){
99         defined($tc)
100             || $class->throw_error("You cannot have coercion without specifying a type constraint on attribute ($name)");
101
102         $args->{weak_ref}
103             && $class->throw_error("You cannot have a weak reference to a coerced value on attribute ($name)");
104     }
105
106     if ($args->{lazy_build}) {
107         exists($args->{default})
108             && $class->throw_error("You can not use lazy_build and default for the same attribute ($name)");
109
110         $args->{lazy}      = 1;
111         $args->{builder} ||= "_build_${name}";
112         if ($name =~ /^_/) {
113             $args->{clearer}   ||= "_clear${name}";
114             $args->{predicate} ||= "_has${name}";
115         }
116         else {
117             $args->{clearer}   ||= "clear_${name}";
118             $args->{predicate} ||= "has_${name}";
119         }
120     }
121
122     if ($args->{auto_deref}) {
123         defined($tc)
124             || $class->throw_error("You cannot auto-dereference without specifying a type constraint on attribute ($name)");
125
126         ( $tc->is_a_type_of('ArrayRef') || $tc->is_a_type_of('HashRef') )
127             || $class->throw_error("You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute ($name)");
128     }
129
130     if (exists $args->{trigger}) {
131         ('CODE' eq ref $args->{trigger})
132             || $class->throw_error("Trigger must be a CODE ref on attribute ($name)");
133     }
134
135     if ($args->{lazy}) {
136         (exists $args->{default} || defined $args->{builder})
137             || $class->throw_error("You cannot have lazy attribute ($name) without specifying a default value for it");
138     }
139
140     my $instance = bless $args, $class;
141
142     # extra attributes
143     if($class ne __PACKAGE__){
144         $class->meta->_initialize_instance($instance, $args);
145     }
146
147 # XXX: there is no fast way to check attribute validity
148 #    my @bad = ...;
149 #    if(@bad){
150 #        @bad = sort @bad;
151 #        Carp::cluck("Found unknown argument(s) passed to '$name' attribute constructor in '$class': @bad");
152 #    }
153
154     return $instance
155 }
156
157 sub does {
158     my ($self, $role_name) = @_;
159     my $meta = Mouse::Meta::Class->initialize(ref($self) || $self);
160
161     (defined $role_name)
162         || $meta->throw_error("You must supply a role name to does()");
163
164     return $meta->does_role($role_name);
165 };
166
167 # readers
168
169 sub name                 { $_[0]->{name}                   }
170 sub associated_class     { $_[0]->{associated_class}       }
171
172 sub accessor             { $_[0]->{accessor}               }
173 sub reader               { $_[0]->{reader}                 }
174 sub writer               { $_[0]->{writer}                 }
175 sub predicate            { $_[0]->{predicate}              }
176 sub clearer              { $_[0]->{clearer}                }
177 sub handles              { $_[0]->{handles}                }
178
179 sub _is_metadata         { $_[0]->{is}                     }
180 sub is_required          { $_[0]->{required}               }
181 sub default              { $_[0]->{default}                }
182 sub is_lazy              { $_[0]->{lazy}                   }
183 sub is_lazy_build        { $_[0]->{lazy_build}             }
184 sub is_weak_ref          { $_[0]->{weak_ref}               }
185 sub init_arg             { $_[0]->{init_arg}               }
186 sub type_constraint      { $_[0]->{type_constraint}        }
187
188 sub trigger              { $_[0]->{trigger}                }
189 sub builder              { $_[0]->{builder}                }
190 sub should_auto_deref    { $_[0]->{auto_deref}             }
191 sub should_coerce        { $_[0]->{coerce}                 }
192
193 sub get_read_method      { $_[0]->{reader} || $_[0]->{accessor} }
194 sub get_write_method     { $_[0]->{writer} || $_[0]->{accessor} }
195
196 # predicates
197
198 sub has_accessor         { exists $_[0]->{accessor}        }
199 sub has_reader           { exists $_[0]->{reader}          }
200 sub has_writer           { exists $_[0]->{writer}          }
201 sub has_predicate        { exists $_[0]->{predicate}       }
202 sub has_clearer          { exists $_[0]->{clearer}         }
203 sub has_handles          { exists $_[0]->{handles}         }
204
205 sub has_default          { exists $_[0]->{default}         }
206 sub has_type_constraint  { exists $_[0]->{type_constraint} }
207 sub has_trigger          { exists $_[0]->{trigger}         }
208 sub has_builder          { exists $_[0]->{builder}         }
209
210 sub has_read_method      { exists $_[0]->{reader} || exists $_[0]->{accessor} }
211 sub has_write_method     { exists $_[0]->{writer} || exists $_[0]->{accessor} }
212
213 sub _create_args {
214     $_[0]->{_create_args} = $_[1] if @_ > 1;
215     $_[0]->{_create_args}
216 }
217
218 sub accessor_metaclass { 'Mouse::Meta::Method::Accessor' }
219
220 sub interpolate_class_and_new{
221     my($class, $name, $args) = @_;
222
223     if(my $metaclass = delete $args->{metaclass}){
224         $class = Mouse::Util::resolve_metaclass_alias( Attribute => $metaclass );
225     }
226
227
228     if(my $traits_ref = delete $args->{traits}){
229         my @traits;
230         for (my $i = 0; $i < @{$traits_ref}; $i++) {
231             my $trait = Mouse::Util::resolve_metaclass_alias(Attribute => $traits_ref->[$i], trait => 1);
232
233             next if $class->does($trait);
234
235             push @traits, $trait;
236
237             # are there options?
238             push @traits, $traits_ref->[++$i]
239                 if ref($traits_ref->[$i+1]);
240         }
241
242         if (@traits) {
243             $class = Mouse::Meta::Class->create_anon_class(
244                 superclasses => [ $class ],
245                 roles        => \@traits,
246                 cache        => 1,
247             )->name;
248
249             $args->{traits} = \@traits;
250         }
251     }
252
253     return $class->new($name, $args);
254 }
255
256 sub canonicalize_args{
257     my ($self, $name, %args) = @_;
258
259     Carp::cluck("$self->canonicalize_args has been deprecated."
260         . "Use \$self->BUILDARGS instead.");
261
262     return %args;
263 }
264
265 sub create {
266     my ($self, $class, $name, %args) = @_;
267
268     Carp::cluck("$self->create has been deprecated."
269         . "Use \$meta->add_attribute and \$attr->install_accessors instead.");
270
271     # noop
272     return $self;
273 }
274
275 sub verify_against_type_constraint {
276     my ($self, $value) = @_;
277     my $tc = $self->type_constraint;
278     return 1 unless $tc;
279
280     local $_ = $value;
281     return 1 if $tc->check($value);
282
283     $self->verify_type_constraint_error($self->name, $value, $tc);
284 }
285
286 sub verify_type_constraint_error {
287     my($self, $name, $value, $type) = @_;
288     $self->throw_error("Attribute ($name) does not pass the type constraint because: " . $type->get_message($value));
289 }
290
291 sub coerce_constraint { ## my($self, $value) = @_;
292     my $type = $_[0]->{type_constraint}
293         or return $_[1];
294     return Mouse::Util::TypeConstraints->typecast_constraints($_[0]->associated_class->name, $_[0]->type_constraint, $_[1]);
295 }
296
297 sub _canonicalize_handles {
298     my $self    = shift;
299     my $handles = shift;
300
301     if (ref($handles) eq 'HASH') {
302         return %$handles;
303     }
304     elsif (ref($handles) eq 'ARRAY') {
305         return map { $_ => $_ } @$handles;
306     }
307     else {
308         $self->throw_error("Unable to canonicalize the 'handles' option with $handles");
309     }
310 }
311
312 sub clone_and_inherit_options{
313     my $self = shift;
314     my $name = shift;
315
316     return ref($self)->new($name, %{$self}, @_ == 1 ? %{$_[0]} : @_);
317 }
318
319 sub clone_parent {
320     my $self  = shift;
321     my $class = shift;
322     my $name  = shift;
323     my %args  = ($self->get_parent_args($class, $name), @_);
324
325     Carp::cluck("$self->clone_parent has been deprecated."
326         . "Use \$meta->add_attribute and \$attr->install_accessors instead.");
327
328
329     $self->create($class, $name, %args);
330 }
331
332 sub get_parent_args {
333     my $self  = shift;
334     my $class = shift;
335     my $name  = shift;
336
337     for my $super ($class->linearized_isa) {
338         my $super_attr = $super->can("meta") && $super->meta->get_attribute($name)
339             or next;
340         return %{ $super_attr->_create_args };
341     }
342
343     $self->throw_error("Could not find an attribute by the name of '$name' to inherit from");
344 }
345
346 sub install_accessors{
347     my($attribute) = @_;
348
349     my $metaclass       = $attribute->{associated_class};
350     my $generator_class = $attribute->accessor_metaclass;
351
352     foreach my $type(qw(accessor reader writer predicate clearer handles)){
353         if(exists $attribute->{$type}){
354             my $installer    = '_install_' . $type;
355             $generator_class->$installer($attribute, $attribute->{$type}, $metaclass);
356             $attribute->{associated_methods}++;
357         }
358     }
359
360     if($attribute->can('create') != \&create){
361         $attribute->create($metaclass, $attribute->name, %{$attribute});
362     }
363
364     return;
365 }
366
367 sub throw_error{
368     my $self = shift;
369
370     my $metaclass = (ref $self && $self->associated_class) || 'Mouse::Meta::Class';
371     $metaclass->throw_error(@_, depth => 1);
372 }
373
374 1;
375
376 __END__
377
378 =head1 NAME
379
380 Mouse::Meta::Attribute - attribute metaclass
381
382 =head1 METHODS
383
384 =head2 new %args -> Mouse::Meta::Attribute
385
386 Instantiates a new Mouse::Meta::Attribute. Does nothing else.
387
388 =head2 create OwnerClass, AttributeName, %args -> Mouse::Meta::Attribute
389
390 Creates a new attribute in OwnerClass. Accessors and helper methods are
391 installed. Some error checking is done.
392
393 =head2 name -> AttributeName
394
395 =head2 associated_class -> OwnerClass
396
397 =head2 is_required -> Bool
398
399 =head2 default -> Item
400
401 =head2 has_default -> Bool
402
403 =head2 is_lazy -> Bool
404
405 =head2 predicate -> MethodName | Undef
406
407 =head2 has_predicate -> Bool
408
409 =head2 clearer -> MethodName | Undef
410
411 =head2 has_clearer -> Bool
412
413 =head2 handles -> { LocalName => RemoteName }
414
415 =head2 has_handles -> Bool
416
417 =head2 is_weak_ref -> Bool
418
419 =head2 init_arg -> Str
420
421 =head2 type_constraint -> Str
422
423 =head2 has_type_constraint -> Bool
424
425 =head2 trigger => CODE | Undef
426
427 =head2 has_trigger -> Bool
428
429 =head2 builder => MethodName | Undef
430
431 =head2 has_builder -> Bool
432
433 =head2 is_lazy_build => Bool
434
435 =head2 should_auto_deref -> Bool
436
437 Informational methods.
438
439 =head2 verify_against_type_constraint Item -> 1 | ERROR
440
441 Checks that the given value passes this attribute's type constraint. Returns 1
442 on success, otherwise C<confess>es.
443
444 =head2 canonicalize_args Name, %args -> %args
445
446 Canonicalizes some arguments to create. In particular, C<lazy_build> is
447 canonicalized into C<lazy>, C<builder>, etc.
448
449 =head2 validate_args Name, \%args -> 1 | ERROR
450
451 Checks that the arguments to create the attribute (ie those specified by
452 C<has>) are valid.
453
454 =head2 clone_parent OwnerClass, AttributeName, %args -> Mouse::Meta::Attribute
455
456 Creates a new attribute in OwnerClass, inheriting options from parent classes.
457 Accessors and helper methods are installed. Some error checking is done.
458
459 =head2 get_parent_args OwnerClass, AttributeName -> Hash
460
461 Returns the options that the parent class of C<OwnerClass> used for attribute
462 C<AttributeName>.
463
464 =cut
465