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