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