Small fix to improve coverage, I wish Devel::Cover handled a ||= "true value"
[gitmo/Mouse.git] / lib / Mouse / Meta / Attribute.pm
1 #!/usr/bin/env perl
2 package Mouse::Meta::Attribute;
3 use strict;
4 use warnings;
5
6 use Carp 'confess';
7 use Scalar::Util 'blessed';
8
9 sub new {
10     my $class = shift;
11     my %args  = @_;
12
13     my $name = $args{name};
14
15     $args{init_arg} = $name
16         unless exists $args{init_arg};
17
18     $args{is} ||= '';
19
20     if ($args{lazy_build}) {
21         confess("You can not use lazy_build and default for the same attribute $name")
22             if exists $args{default};
23         $args{lazy}      = 1;
24         $args{required}  = 1;
25         $args{builder}   = "_build_${name}"
26             if !exists($args{builder});
27         if ($name =~ /^_/) {
28             $args{clearer}   = "_clear${name}" if !exists($args{clearer});
29             $args{predicate} = "_has${name}" if !exists($args{predicate});
30         }
31         else {
32             $args{clearer}   = "clear_${name}" if !exists($args{clearer});
33             $args{predicate} = "has_${name}" if !exists($args{predicate});
34         }
35     }
36
37     bless \%args, $class;
38 }
39
40 sub name              { $_[0]->{name}            }
41 sub class             { $_[0]->{class}           }
42 sub _is_metadata      { $_[0]->{is}              }
43 sub is_required       { $_[0]->{required}        }
44 sub default           { $_[0]->{default}         }
45 sub is_lazy           { $_[0]->{lazy}            }
46 sub is_lazy_build     { $_[0]->{lazy_build}      }
47 sub predicate         { $_[0]->{predicate}       }
48 sub clearer           { $_[0]->{clearer}         }
49 sub handles           { $_[0]->{handles}         }
50 sub is_weak_ref       { $_[0]->{weak_ref}        }
51 sub init_arg          { $_[0]->{init_arg}        }
52 sub type_constraint   { $_[0]->{type_constraint} }
53 sub trigger           { $_[0]->{trigger}         }
54 sub builder           { $_[0]->{builder}         }
55 sub should_auto_deref { $_[0]->{auto_deref}      }
56
57 sub has_default         { exists $_[0]->{default}         }
58 sub has_predicate       { exists $_[0]->{predicate}       }
59 sub has_clearer         { exists $_[0]->{clearer}         }
60 sub has_handles         { exists $_[0]->{handles}         }
61 sub has_type_constraint { exists $_[0]->{type_constraint} }
62 sub has_trigger         { exists $_[0]->{trigger}         }
63 sub has_builder         { exists $_[0]->{builder}         }
64
65 sub _create_args {
66     $_[0]->{_create_args} = $_[1] if @_ > 1;
67     $_[0]->{_create_args}
68 }
69
70 sub generate_accessor {
71     my $attribute = shift;
72
73     my $name       = $attribute->name;
74     my $key        = $name;
75     my $default    = $attribute->default;
76     my $trigger    = $attribute->trigger;
77     my $type       = $attribute->type_constraint;
78     my $constraint = $attribute->find_type_constraint;
79     my $builder    = $attribute->builder;
80
81     my $accessor = 'sub {
82         my $self = shift;';
83
84     if ($attribute->_is_metadata eq 'rw') {
85         $accessor .= 'if (@_) {
86             local $_ = $_[0];';
87
88         if ($constraint) {
89             $accessor .= 'do {
90                 my $display = defined($_) ? overload::StrVal($_) : "undef";
91                 Carp::confess("Attribute ($name) does not pass the type constraint because: Validation failed for \'$type\' failed with value $display") unless $constraint->();
92             };'
93         }
94
95         $accessor .= '$self->{$key} = $_;';
96
97         if ($attribute->is_weak_ref) {
98             $accessor .= 'Scalar::Util::weaken($self->{$key}) if ref($self->{$key});';
99         }
100
101         if ($trigger) {
102             $accessor .= '$trigger->($self, $_, $attribute);';
103         }
104
105         $accessor .= '}';
106     }
107     else {
108         $accessor .= 'confess "Cannot assign a value to a read-only accessor" if @_;';
109     }
110
111     if ($attribute->is_lazy) {
112         $accessor .= '$self->{$key} = ';
113
114         $accessor .= $attribute->has_builder
115                    ? '$self->$builder'
116                      : ref($default) eq 'CODE'
117                      ? '$default->($self)'
118                      : '$default';
119
120         $accessor .= ' if !exists($self->{$key});';
121     }
122
123     if ($attribute->should_auto_deref) {
124         if ($attribute->type_constraint eq 'ArrayRef') {
125             $accessor .= 'if (wantarray) {
126                 return @{ $self->{$key} || [] };
127             }';
128         }
129         else {
130             $accessor .= 'if (wantarray) {
131                 return %{ $self->{$key} || {} };
132             }';
133         }
134     }
135
136     $accessor .= 'return $self->{$key};
137     }';
138
139     return eval $accessor;
140 }
141
142 sub generate_predicate {
143     my $attribute = shift;
144     my $key = $attribute->name;
145
146     my $predicate = 'sub { exists($_[0]->{$key}) }';
147
148     return eval $predicate;
149 }
150
151 sub generate_clearer {
152     my $attribute = shift;
153     my $key = $attribute->name;
154
155     my $predicate = 'sub { delete($_[0]->{$key}) }';
156
157     return eval $predicate;
158 }
159
160 sub generate_handles {
161     my $attribute = shift;
162     my $reader = $attribute->name;
163     my %handles = $attribute->_canonicalize_handles($attribute->handles);
164
165     my %method_map;
166
167     for my $local_method (keys %handles) {
168         my $remote_method = $handles{$local_method};
169
170         my $method = 'sub {
171             my $self = shift;
172             $self->$reader->$remote_method(@_)
173         }';
174
175         $method_map{$local_method} = eval $method;
176     }
177
178     return \%method_map;
179 }
180
181 sub create {
182     my ($self, $class, $name, %args) = @_;
183
184     $args{name} = $name;
185     $args{class} = $class;
186
187     $self->validate_args($name, %args);
188
189     $args{type_constraint} = delete $args{isa}
190         if exists $args{isa};
191
192     my $attribute = $self->new(%args);
193     $attribute->_create_args(\%args);
194
195     my $meta = $class->meta;
196
197     $meta->add_attribute($attribute);
198
199     # install an accessor
200     if ($attribute->_is_metadata eq 'rw' || $attribute->_is_metadata eq 'ro') {
201         my $accessor = $attribute->generate_accessor;
202         no strict 'refs';
203         *{ $class . '::' . $name } = $accessor;
204     }
205
206     for my $method (qw/predicate clearer/) {
207         my $predicate = "has_$method";
208         if ($attribute->$predicate) {
209             my $generator = "generate_$method";
210             my $coderef = $attribute->$generator;
211             no strict 'refs';
212             *{ $class . '::' . $attribute->$method } = $coderef;
213         }
214     }
215
216     if ($attribute->has_handles) {
217         my $method_map = $attribute->generate_handles;
218         for my $method_name (keys %$method_map) {
219             no strict 'refs';
220             *{ $class . '::' . $method_name } = $method_map->{$method_name};
221         }
222     }
223
224     return $attribute;
225 }
226
227 sub validate_args {
228     my $self = shift;
229     my $name = shift;
230     my %args = @_;
231
232     confess "You cannot have lazy attribute ($name) without specifying a default value for it"
233         if $args{lazy} && !exists($args{default}) && !exists($args{builder});
234
235     confess "References are not allowed as default values, you must wrap the default of '$name' in a CODE reference (ex: sub { [] } and not [])"
236         if ref($args{default})
237         && ref($args{default}) ne 'CODE';
238
239     confess "You cannot auto-dereference without specifying a type constraint on attribute $name"
240         if $args{auto_deref} && !exists($args{isa});
241
242     confess "You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute $name"
243         if $args{auto_deref}
244         && $args{isa} ne 'ArrayRef'
245         && $args{isa} ne 'HashRef';
246
247     return 1;
248 }
249
250 sub find_type_constraint {
251     my $self = shift;
252     my $type = $self->type_constraint;
253
254     return unless $type;
255
256     my $checker = Mouse::TypeRegistry->optimized_constraints->{$type};
257     return $checker if $checker;
258
259     return sub { blessed($_) && blessed($_) eq $type };
260 }
261
262 sub verify_type_constraint {
263     my $self = shift;
264     local $_ = shift;
265
266     my $type = $self->type_constraint
267         or return 1;
268     my $constraint = $self->find_type_constraint;
269
270     return 1 if $constraint->($_);
271
272     my $name = $self->name;
273     my $display = defined($_) ? overload::StrVal($_) : 'undef';
274     Carp::confess("Attribute ($name) does not pass the type constraint because: Validation failed for \'$type\' failed with value $display");
275 }
276
277 sub _canonicalize_handles {
278     my $self    = shift;
279     my $handles = shift;
280
281     if (ref($handles) eq 'HASH') {
282         return %$handles;
283     }
284     elsif (ref($handles) eq 'ARRAY') {
285         return map { $_ => $_ } @$handles;
286     }
287     else {
288         confess "Unable to canonicalize the 'handles' option with $handles";
289     }
290 }
291
292 sub clone_parent {
293     my $self  = shift;
294     my $class = shift;
295     my $name  = shift;
296     my %args  = ($self->get_parent_args($class, $name), @_);
297
298     $self->create($class, $name, %args);
299 }
300
301 sub get_parent_args {
302     my $self  = shift;
303     my $class = shift;
304     my $name  = shift;
305
306     for my $super ($class->meta->linearized_isa) {
307         my $super_attr = $super->can("meta") && $super->meta->get_attribute($name)
308             or next;
309         return %{ $super_attr->_create_args };
310     }
311
312     confess "Could not find an attribute by the name of '$name' to inherit from";
313 }
314
315 1;
316
317 __END__
318
319 =head1 NAME
320
321 Mouse::Meta::Attribute - attribute metaclass
322
323 =head1 METHODS
324
325 =head2 new %args -> Mouse::Meta::Attribute
326
327 Instantiates a new Mouse::Meta::Attribute. Does nothing else.
328
329 =head2 create OwnerClass, AttributeName, %args -> Mouse::Meta::Attribute
330
331 Creates a new attribute in OwnerClass. Accessors and helper methods are
332 installed. Some error checking is done.
333
334 =head2 name -> AttributeName
335
336 =head2 class -> OwnerClass
337
338 =head2 is_required -> Bool
339
340 =head2 default -> Item
341
342 =head2 has_default -> Bool
343
344 =head2 is_lazy -> Bool
345
346 =head2 predicate -> MethodName | Undef
347
348 =head2 has_predicate -> Bool
349
350 =head2 clearer -> MethodName | Undef
351
352 =head2 has_clearer -> Bool
353
354 =head2 handles -> { LocalName => RemoteName }
355
356 =head2 has_handles -> Bool
357
358 =head2 is_weak_ref -> Bool
359
360 =head2 init_arg -> Str
361
362 =head2 type_constraint -> Str
363
364 =head2 has_type_constraint -> Bool
365
366 =head2 trigger => CODE | Undef
367
368 =head2 has_trigger -> Bool
369
370 =head2 builder => MethodName | Undef
371
372 =head2 has_builder -> Bool
373
374 =head2 is_lazy_build => Bool
375
376 =head2 should_auto_deref -> Bool
377
378 Informational methods.
379
380 =head2 generate_accessor -> CODE
381
382 Creates a new code reference for the attribute's accessor.
383
384 =head2 generate_predicate -> CODE
385
386 Creates a new code reference for the attribute's predicate.
387
388 =head2 generate_clearer -> CODE
389
390 Creates a new code reference for the attribute's clearer.
391
392 =head2 generate_handles -> { MethodName => CODE }
393
394 Creates a new code reference for each of the attribute's handles methods.
395
396 =head2 find_type_constraint -> CODE
397
398 Returns a code reference which can be used to check that a given value passes
399 this attribute's type constraint;
400
401 =head2 verify_type_constraint Item -> 1 | ERROR
402
403 Checks that the given value passes this attribute's type constraint. Returns 1
404 on success, otherwise C<confess>es.
405
406 =cut
407