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