working-on-it
[gitmo/Class-MOP.git] / lib / Class / MOP / Attribute.pm
1
2 package Class::MOP::Attribute;
3
4 use strict;
5 use warnings;
6
7 use Carp         'confess';
8 use Scalar::Util 'blessed', 'reftype', 'weaken';
9
10 our $VERSION = '0.08';
11
12 sub meta { 
13     require Class::MOP::Class;
14     Class::MOP::Class->initialize(blessed($_[0]) || $_[0]);
15 }
16
17 # NOTE: (meta-circularity)
18 # This method will be replaces in the 
19 # boostrap section of Class::MOP, by 
20 # a new version which uses the 
21 # &Class::MOP::Class::construct_instance
22 # method to build an attribute meta-object
23 # which itself is described with attribute
24 # meta-objects. 
25 #     - Ain't meta-circularity grand? :)
26 sub new {
27     my $class   = shift;
28     my $name    = shift;
29     my %options = @_;    
30         
31     (defined $name && $name)
32         || confess "You must provide a name for the attribute";
33     $options{init_arg} = $name 
34         if not exists $options{init_arg};
35             
36     bless {
37         name      => $name,
38         accessor  => $options{accessor},
39         reader    => $options{reader},
40         writer    => $options{writer},
41         predicate => $options{predicate},
42         init_arg  => $options{init_arg},
43         default   => $options{default},
44         # keep a weakened link to the 
45         # class we are associated with
46         associated_class => undef,
47     } => $class;
48 }
49
50 # NOTE:
51 # this is a primative (and kludgy) clone operation 
52 # for now, it will be repleace in the Class::MOP
53 # bootstrap with a proper one, however we know 
54 # that this one will work fine for now.
55 sub clone {
56     my $self    = shift;
57     my %options = @_;
58     (blessed($self))
59         || confess "Can only clone an instance";
60     return bless { %{$self}, %options } => blessed($self);
61 }
62
63 sub initialize_instance_slot {
64     my ($self, $meta_instance, $instance, $params) = @_;
65     my $init_arg = $self->{init_arg};
66     # try to fetch the init arg from the %params ...
67     my $val;        
68     $val = $params->{$init_arg} if exists $params->{$init_arg};
69     # if nothing was in the %params, we can use the 
70     # attribute's default value (if it has one)
71     if (!defined $val && defined $self->{default}) {
72         $val = $self->default($instance);
73     }
74     $self->associated_class
75          ->get_meta_instance
76          ->set_slot_value($instance, $self->name, $val);
77 }
78
79 # NOTE:
80 # the next bunch of methods will get bootstrapped 
81 # away in the Class::MOP bootstrapping section
82
83 sub name { $_[0]->{name} }
84
85 sub associated_class { $_[0]->{associated_class} }
86
87 sub has_accessor  { defined($_[0]->{accessor})  ? 1 : 0 }
88 sub has_reader    { defined($_[0]->{reader})    ? 1 : 0 }
89 sub has_writer    { defined($_[0]->{writer})    ? 1 : 0 }
90 sub has_predicate { defined($_[0]->{predicate}) ? 1 : 0 }
91 sub has_init_arg  { defined($_[0]->{init_arg})  ? 1 : 0 }
92 sub has_default   { defined($_[0]->{default})   ? 1 : 0 }
93
94 sub accessor  { $_[0]->{accessor}  } 
95 sub reader    { $_[0]->{reader}    }
96 sub writer    { $_[0]->{writer}    }
97 sub predicate { $_[0]->{predicate} }
98 sub init_arg  { $_[0]->{init_arg}  }
99
100 # end bootstrapped away method section.
101 # (all methods below here are kept intact)
102
103 sub default { 
104     my $self = shift;
105     if (reftype($self->{default}) && reftype($self->{default}) eq 'CODE') {
106         # if the default is a CODE ref, then 
107         # we pass in the instance and default
108         # can return a value based on that 
109         # instance. Somewhat crude, but works.
110         return $self->{default}->(shift);
111     }           
112     $self->{default};
113 }
114
115 # slots
116
117 sub slots { (shift)->name }
118
119 # class association 
120
121 sub attach_to_class {
122     my ($self, $class) = @_;
123     (blessed($class) && $class->isa('Class::MOP::Class'))
124         || confess "You must pass a Class::MOP::Class instance (or a subclass)";
125     weaken($self->{associated_class} = $class);    
126 }
127
128 sub detach_from_class {
129     my $self = shift;
130     $self->{associated_class} = undef;        
131 }
132
133 ## Method generation helpers
134
135 sub generate_accessor_method {
136     my $self = shift;
137     my $meta_class = $self->associated_class;    
138     my $attr_name  = $self->name;
139     return sub {
140         my $meta_instance = $meta_class->get_meta_instance;
141         $meta_instance->set_slot_value($_[0], $attr_name, $_[1]) if scalar(@_) == 2;
142         $meta_instance->get_slot_value($_[0], $attr_name);
143     };
144 }
145
146 sub generate_reader_method {
147     my $self = shift;
148     my $meta_class = $self->associated_class;    
149     my $attr_name  = $self->name;
150     return sub { 
151         confess "Cannot assign a value to a read-only accessor" if @_ > 1;
152         $meta_class->get_meta_instance
153                    ->get_slot_value($_[0], $attr_name); 
154     };   
155 }
156
157 sub generate_writer_method {
158     my $self = shift;
159     my $meta_class = $self->associated_class;    
160     my $attr_name  = $self->name;
161     return sub { 
162         $meta_class->get_meta_instance
163                    ->set_slot_value($_[0], $attr_name, $_[1]);
164     };
165 }
166
167 sub generate_predicate_method {
168     my $self = shift;
169     my $meta_class = $self->associated_class;    
170     my $attr_name  = $self->name;
171     return sub { 
172         defined $meta_class->get_meta_instance
173                            ->get_slot_value($_[0], $attr_name) ? 1 : 0;
174     };
175 }
176
177 sub process_accessors {
178     my ($self, $type, $accessor) = @_;
179     if (reftype($accessor)) {
180         (reftype($accessor) eq 'HASH')
181             || confess "bad accessor/reader/writer/predicate format, must be a HASH ref";
182         my ($name, $method) = each %{$accessor};
183         return ($name, Class::MOP::Attribute::Accessor->wrap($method));        
184     }
185     else {
186         my $generator = $self->can('generate_' . $type . '_method');
187         ($generator)
188             || confess "There is no method generator for the type='$type'";
189         if (my $method = $self->$generator($self->name)) {
190             return ($accessor => Class::MOP::Attribute::Accessor->wrap($method));            
191         }
192         confess "Could not create the '$type' method for " . $self->name . " because : $@";
193     }    
194 }
195
196 sub install_accessors {
197     my $self  = shift;
198     my $class = $self->associated_class;
199     
200     $class->add_method(
201         $self->process_accessors('accessor' => $self->accessor())
202     ) if $self->has_accessor();
203
204     $class->add_method(            
205         $self->process_accessors('reader' => $self->reader())
206     ) if $self->has_reader();
207
208     $class->add_method(
209         $self->process_accessors('writer' => $self->writer())
210     ) if $self->has_writer();
211
212     $class->add_method(
213         $self->process_accessors('predicate' => $self->predicate())
214     ) if $self->has_predicate();
215     return;
216 }
217
218 {
219     my $_remove_accessor = sub {
220         my ($accessor, $class) = @_;
221         if (reftype($accessor) && reftype($accessor) eq 'HASH') {
222             ($accessor) = keys %{$accessor};
223         }        
224         my $method = $class->get_method($accessor);   
225         $class->remove_method($accessor) 
226             if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
227     };
228     
229     sub remove_accessors {
230         my $self = shift;
231         $_remove_accessor->($self->accessor(),  $self->associated_class()) if $self->has_accessor();
232         $_remove_accessor->($self->reader(),    $self->associated_class()) if $self->has_reader();
233         $_remove_accessor->($self->writer(),    $self->associated_class()) if $self->has_writer();
234         $_remove_accessor->($self->predicate(), $self->associated_class()) if $self->has_predicate();
235         return;                        
236     }
237
238 }
239
240 package Class::MOP::Attribute::Accessor;
241
242 use strict;
243 use warnings;
244
245 use Class::MOP::Method;
246
247 our $VERSION = '0.01';
248
249 our @ISA = ('Class::MOP::Method');
250
251 1;
252
253 __END__
254
255 =pod
256
257 =head1 NAME 
258
259 Class::MOP::Attribute - Attribute Meta Object
260
261 =head1 SYNOPSIS
262   
263   Class::MOP::Attribute->new('$foo' => (
264       accessor  => 'foo',        # dual purpose get/set accessor
265       predicate => 'has_foo'     # predicate check for defined-ness      
266       init_arg  => '-foo',       # class->new will look for a -foo key
267       default   => 'BAR IS BAZ!' # if no -foo key is provided, use this
268   ));
269   
270   Class::MOP::Attribute->new('$.bar' => (
271       reader    => 'bar',        # getter
272       writer    => 'set_bar',    # setter     
273       predicate => 'has_bar'     # predicate check for defined-ness      
274       init_arg  => ':bar',       # class->new will look for a :bar key
275       # no default value means it is undef
276   ));
277
278 =head1 DESCRIPTION
279
280 The Attribute Protocol is almost entirely an invention of this module,
281 and is completely optional to this MOP. This is because Perl 5 does not 
282 have consistent notion of what is an attribute of a class. There are 
283 so many ways in which this is done, and very few (if any) are 
284 easily discoverable by this module.
285
286 So, all that said, this module attempts to inject some order into this 
287 chaos, by introducing a consistent API which can be used to create 
288 object attributes.
289
290 =head1 METHODS
291
292 =head2 Creation
293
294 =over 4
295
296 =item B<new ($name, ?%options)>
297
298 An attribute must (at the very least), have a C<$name>. All other 
299 C<%options> are contained added as key-value pairs. Acceptable keys
300 are as follows:
301
302 =over 4
303
304 =item I<init_arg>
305
306 This should be a string value representing the expected key in 
307 an initialization hash. For instance, if we have an I<init_arg> 
308 value of C<-foo>, then the following code will Just Work.
309
310   MyClass->meta->construct_instance(-foo => "Hello There");
311
312 In an init_arg is not assigned, it will automatically use the 
313 value of C<$name>.
314
315 =item I<default>
316
317 The value of this key is the default value which 
318 C<Class::MOP::Class::construct_instance> will initialize the 
319 attribute to. 
320
321 B<NOTE:>
322 If the value is a simple scalar (string or number), then it can 
323 be just passed as is. However, if you wish to initialize it with 
324 a HASH or ARRAY ref, then you need to wrap that inside a CODE 
325 reference, like so:
326
327   Class::MOP::Attribute->new('@foo' => (
328       default => sub { [] },
329   ));
330   
331   # or ...  
332   
333   Class::MOP::Attribute->new('%foo' => (
334       default => sub { {} },
335   ));  
336
337 If you wish to initialize an attribute with a CODE reference 
338 itself, then you need to wrap that in a subroutine as well, like
339 so:
340   
341   Class::MOP::Attribute->new('&foo' => (
342       default => sub { sub { print "Hello World" } },
343   ));
344
345 And lastly, if the value of your attribute is dependent upon 
346 some other aspect of the instance structure, then you can take 
347 advantage of the fact that when the I<default> value is a CODE 
348 reference, it is passed the raw (unblessed) instance structure 
349 as it's only argument. So you can do things like this:
350
351   Class::MOP::Attribute->new('$object_identity' => (
352       default => sub { Scalar::Util::refaddr($_[0]) },
353   ));
354
355 This last feature is fairly limited as there is no gurantee of 
356 the order of attribute initializations, so you cannot perform 
357 any kind of dependent initializations. However, if this is 
358 something you need, you could subclass B<Class::MOP::Class> and 
359 this class to acheive it. However, this is currently left as 
360 an exercise to the reader :).
361
362 =back
363
364 The I<accessor>, I<reader>, I<writer> and I<predicate> keys can 
365 contain either; the name of the method and an appropriate default 
366 one will be generated for you, B<or> a HASH ref containing exactly one 
367 key (which will be used as the name of the method) and one value, 
368 which should contain a CODE reference which will be installed as 
369 the method itself.
370
371 =over 4
372
373 =item I<accessor>
374
375 The I<accessor> is a standard perl-style read/write accessor. It will 
376 return the value of the attribute, and if a value is passed as an argument, 
377 it will assign that value to the attribute.
378
379 B<NOTE:>
380 This method will properly handle the following code, by assigning an 
381 C<undef> value to the attribute.
382
383   $object->set_something(undef);
384
385 =item I<reader>
386
387 This is a basic read-only accessor, it will just return the value of 
388 the attribute.
389
390 =item I<writer>
391
392 This is a basic write accessor, it accepts a single argument, and 
393 assigns that value to the attribute. This method does not intentially 
394 return a value, however perl will return the result of the last 
395 expression in the subroutine, which returns in this returning the 
396 same value that it was passed. 
397
398 B<NOTE:>
399 This method will properly handle the following code, by assigning an 
400 C<undef> value to the attribute.
401
402   $object->set_something();
403
404 =item I<predicate>
405
406 This is a basic test to see if the value of the attribute is not 
407 C<undef>. It will return true (C<1>) if the attribute's value is 
408 defined, and false (C<0>) otherwise.
409
410 =back
411
412 =item B<clone (%options)>
413
414 =item B<initialize_instance_slot ($instance, $params)>
415
416 =back 
417
418 =head2 Informational
419
420 These are all basic read-only value accessors for the values 
421 passed into C<new>. I think they are pretty much self-explanitory.
422
423 =over 4
424
425 =item B<name>
426
427 =item B<accessor>
428
429 =item B<reader>
430
431 =item B<writer>
432
433 =item B<predicate>
434
435 =item B<init_arg>
436
437 =item B<default (?$instance)>
438
439 As noted in the documentation for C<new> above, if the I<default> 
440 value is a CODE reference, this accessor will pass a single additional
441 argument C<$instance> into it and return the value.
442
443 =item B<slots>
444
445 Returns a list of slots required by the attribute. This is usually 
446 just one, which is the name of the attribute.
447
448 =back
449
450 =head2 Informational predicates
451
452 These are all basic predicate methods for the values passed into C<new>.
453
454 =over 4
455
456 =item B<has_accessor>
457
458 =item B<has_reader>
459
460 =item B<has_writer>
461
462 =item B<has_predicate>
463
464 =item B<has_init_arg>
465
466 =item B<has_default>
467
468 =back
469
470 =head2 Class association
471
472 =over 4
473
474 =item B<associated_class>
475
476 =item B<attach_to_class ($class)>
477
478 =item B<detach_from_class>
479
480 =item B<slot_name>
481
482 =item B<allocate_slots>
483
484 =item B<deallocate_slots>
485
486 =back
487
488 =head2 Attribute Accessor generation
489
490 =over 4
491
492 =item B<install_accessors>
493
494 This allows the attribute to generate and install code for it's own 
495 I<accessor/reader/writer/predicate> methods. This is called by 
496 C<Class::MOP::Class::add_attribute>.
497
498 This method will call C<process_accessors> for each of the possible 
499 method types (accessor, reader, writer & predicate).
500
501 =item B<process_accessors ($type, $value)>
502
503 This takes a C<$type> (accessor, reader, writer or predicate), and 
504 a C<$value> (the value passed into the constructor for each of the
505 different types). It will then either generate the method itself 
506 (using the C<generate_*_method> methods listed below) or it will 
507 use the custom method passed through the constructor. 
508
509 =over 4
510
511 =item B<generate_accessor_method>
512
513 =item B<generate_predicate_method>
514
515 =item B<generate_reader_method>
516
517 =item B<generate_writer_method>
518
519 =back
520
521 =item B<remove_accessors>
522
523 This allows the attribute to remove the method for it's own 
524 I<accessor/reader/writer/predicate>. This is called by 
525 C<Class::MOP::Class::remove_attribute>.
526
527 =back
528
529 =head2 Introspection
530
531 =over 4
532
533 =item B<meta>
534
535 This will return a B<Class::MOP::Class> instance which is related 
536 to this class.
537
538 It should also be noted that B<Class::MOP> will actually bootstrap 
539 this module by installing a number of attribute meta-objects into 
540 it's metaclass. This will allow this class to reap all the benifits 
541 of the MOP when subclassing it. 
542
543 =back
544
545 =head1 AUTHOR
546
547 Stevan Little E<lt>stevan@iinteractive.comE<gt>
548
549 =head1 COPYRIGHT AND LICENSE
550
551 Copyright 2006 by Infinity Interactive, Inc.
552
553 L<http://www.iinteractive.com>
554
555 This library is free software; you can redistribute it and/or modify
556 it under the same terms as Perl itself. 
557
558 =cut