Move component-group test to its own testfile
[p5sagit/Class-Accessor-Grouped.git] / lib / Class / Accessor / Grouped.pm
CommitLineData
963a69a5 1package Class::Accessor::Grouped;
2use strict;
3use warnings;
a0bce8bc 4use Carp ();
a0bce8bc 5use Scalar::Util ();
8787799c 6use MRO::Compat;
1ee74bdd 7use Sub::Name ();
331e820d 8
9f562456 9our $VERSION = '0.09005';
15cf8e32 10$VERSION = eval $VERSION;
963a69a5 11
9f562456 12# Class::XSAccessor is segfaulting on win32, so be careful
13# Win32 users can set $hasXS to try to use it anyway
14
a2537c55 15our $hasXS;
af169484 16
a2537c55 17sub _hasXS {
a2537c55 18 if (not defined $hasXS) {
19 $hasXS = 0;
20
9f562456 21 if ($^O ne 'MSWin32') {
22 eval {
23 require Class::XSAccessor;
24 $hasXS = 1;
25 };
26 }
a2537c55 27 }
28
29 return $hasXS;
30}
31
963a69a5 32=head1 NAME
33
1ad8d8c6 34Class::Accessor::Grouped - Lets you build groups of accessors
963a69a5 35
36=head1 SYNOPSIS
37
38=head1 DESCRIPTION
39
40This class lets you build groups of accessors that will call different
41getters and setters.
42
43=head1 METHODS
44
45=head2 mk_group_accessors
46
47=over 4
48
49=item Arguments: $group, @fieldspec
50
51Returns: none
52
53=back
54
55Creates a set of accessors in a given group.
56
57$group is the name of the accessor group for the generated accessors; they
58will call get_$group($field) on get and set_$group($field, $value) on set.
59
22fa6720 60If you want to mimic Class::Accessor's mk_accessors $group has to be 'simple'
61to tell Class::Accessor::Grouped to use its own get_simple and set_simple
62methods.
63
963a69a5 64@fieldspec is a list of field/accessor names; if a fieldspec is a scalar
65this is used as both field and accessor name, if a listref it is expected to
66be of the form [ $accessor, $field ].
67
68=cut
69
70sub mk_group_accessors {
71 my ($self, $group, @fields) = @_;
72
73 $self->_mk_group_accessors('make_group_accessor', $group, @fields);
74 return;
75}
76
77
78{
79 no strict 'refs';
80 no warnings 'redefine';
81
82 sub _mk_group_accessors {
83 my($self, $maker, $group, @fields) = @_;
a0bce8bc 84 my $class = Scalar::Util::blessed $self || $self;
963a69a5 85
86 # So we don't have to do lots of lookups inside the loop.
87 $maker = $self->can($maker) unless ref $maker;
9f562456 88
9540f4e4 89 my $hasXS = _hasXS();
963a69a5 90
91 foreach my $field (@fields) {
92 if( $field eq 'DESTROY' ) {
a0bce8bc 93 Carp::carp("Having a data accessor named DESTROY in ".
963a69a5 94 "'$class' is unwise.");
95 }
96
97 my $name = $field;
98
99 ($name, $field) = @$field if ref $field;
9f562456 100
963a69a5 101 my $alias = "_${name}_accessor";
1ee74bdd 102 my $full_name = join('::', $class, $name);
103 my $full_alias = join('::', $class, $alias);
9540f4e4 104 if ( $hasXS && $group eq 'simple' ) {
46f1ef81 105 require Class::XSAccessor;
96bd9337 106 Class::XSAccessor->import({
107 replace => 1,
108 class => $class,
109 accessors => {
110 $name => $field,
111 $alias => $field,
112 },
113 });
9540f4e4 114 }
115 else {
116 my $accessor = $self->$maker($group, $field);
117 my $alias_accessor = $self->$maker($group, $field);
9f562456 118
9540f4e4 119 *$full_name = Sub::Name::subname($full_name, $accessor);
120 #unless defined &{$class."\:\:$field"}
9f562456 121
9540f4e4 122 *$full_alias = Sub::Name::subname($full_alias, $alias_accessor);
123 #unless defined &{$class."\:\:$alias"}
124 }
963a69a5 125 }
126 }
127}
128
129=head2 mk_group_ro_accessors
130
131=over 4
132
133=item Arguments: $group, @fieldspec
134
135Returns: none
136
137=back
138
139Creates a set of read only accessors in a given group. Identical to
a557f8ad 140L</mk_group_accessors> but accessors will throw an error if passed a value
963a69a5 141rather than setting the value.
142
143=cut
144
145sub mk_group_ro_accessors {
146 my($self, $group, @fields) = @_;
147
148 $self->_mk_group_accessors('make_group_ro_accessor', $group, @fields);
149}
150
151=head2 mk_group_wo_accessors
152
153=over 4
154
155=item Arguments: $group, @fieldspec
156
157Returns: none
158
159=back
160
161Creates a set of write only accessors in a given group. Identical to
a557f8ad 162L</mk_group_accessors> but accessors will throw an error if not passed a
963a69a5 163value rather than getting the value.
164
165=cut
166
167sub mk_group_wo_accessors {
168 my($self, $group, @fields) = @_;
169
170 $self->_mk_group_accessors('make_group_wo_accessor', $group, @fields);
171}
172
173=head2 make_group_accessor
174
175=over 4
176
177=item Arguments: $group, $field
178
179Returns: $sub (\CODE)
180
181=back
182
183Returns a single accessor in a given group; called by mk_group_accessors
184for each entry in @fieldspec.
185
186=cut
187
188sub make_group_accessor {
189 my ($class, $group, $field) = @_;
190
191 my $set = "set_$group";
192 my $get = "get_$group";
193
6a4c729f 194 $field =~ s/'/\\'/g;
195
a0bce8bc 196 # eval for faster fastiness
43db6fd3 197 my $code = eval "sub {
a0bce8bc 198 if(\@_ > 1) {
199 return shift->$set('$field', \@_);
963a69a5 200 }
201 else {
a0bce8bc 202 return shift->$get('$field');
963a69a5 203 }
43db6fd3 204 };";
205 Carp::croak $@ if $@;
206
207 return $code;
963a69a5 208}
209
210=head2 make_group_ro_accessor
211
212=over 4
213
214=item Arguments: $group, $field
215
216Returns: $sub (\CODE)
217
218=back
219
220Returns a single read-only accessor in a given group; called by
221mk_group_ro_accessors for each entry in @fieldspec.
222
223=cut
224
225sub make_group_ro_accessor {
226 my($class, $group, $field) = @_;
227
228 my $get = "get_$group";
229
6a4c729f 230 $field =~ s/'/\\'/g;
231
43db6fd3 232 my $code = eval "sub {
a0bce8bc 233 if(\@_ > 1) {
234 my \$caller = caller;
235 Carp::croak(\"'\$caller' cannot alter the value of '$field' on \".
236 \"objects of class '$class'\");
963a69a5 237 }
238 else {
a0bce8bc 239 return shift->$get('$field');
963a69a5 240 }
43db6fd3 241 };";
242 Carp::croak $@ if $@;
243
244 return $code;
963a69a5 245}
246
247=head2 make_group_wo_accessor
248
249=over 4
250
251=item Arguments: $group, $field
252
253Returns: $sub (\CODE)
254
255=back
256
257Returns a single write-only accessor in a given group; called by
258mk_group_wo_accessors for each entry in @fieldspec.
259
260=cut
261
262sub make_group_wo_accessor {
263 my($class, $group, $field) = @_;
264
265 my $set = "set_$group";
266
6a4c729f 267 $field =~ s/'/\\'/g;
268
43db6fd3 269 my $code = eval "sub {
a0bce8bc 270 unless (\@_ > 1) {
271 my \$caller = caller;
272 Carp::croak(\"'\$caller' cannot access the value of '$field' on \".
273 \"objects of class '$class'\");
963a69a5 274 }
275 else {
a0bce8bc 276 return shift->$set('$field', \@_);
963a69a5 277 }
43db6fd3 278 };";
279 Carp::croak $@ if $@;
280
281 return $code;
963a69a5 282}
283
284=head2 get_simple
285
286=over 4
287
288=item Arguments: $field
289
290Returns: $value
291
292=back
293
294Simple getter for hash-based objects which returns the value for the field
295name passed as an argument.
296
297=cut
298
299sub get_simple {
a0bce8bc 300 return $_[0]->{$_[1]};
963a69a5 301}
302
303=head2 set_simple
304
305=over 4
306
307=item Arguments: $field, $new_value
308
309Returns: $new_value
310
311=back
312
313Simple setter for hash-based objects which sets and then returns the value
314for the field name passed as an argument.
315
316=cut
317
318sub set_simple {
a0bce8bc 319 return $_[0]->{$_[1]} = $_[2];
963a69a5 320}
321
e6f2a0fd 322
323=head2 get_inherited
324
325=over 4
326
327=item Arguments: $field
328
329Returns: $value
330
331=back
332
331e820d 333Simple getter for Classes and hash-based objects which returns the value for
334the field name passed as an argument. This behaves much like
335L<Class::Data::Accessor> where the field can be set in a base class,
336inherited and changed in subclasses, and inherited and changed for object
337instances.
e6f2a0fd 338
339=cut
340
341sub get_inherited {
a49c32d9 342 my $class;
e6f2a0fd 343
62cf9924 344 if ( ($class = ref $_[0]) && Scalar::Util::blessed $_[0]) {
345 if (Scalar::Util::reftype $_[0] eq 'HASH') {
346 return $_[0]->{$_[1]} if exists $_[0]->{$_[1]};
347 }
348 else {
349 Carp::croak('Cannot get inherited value on an object instance that is not hash-based');
350 }
351 }
352 else {
a0bce8bc 353 $class = $_[0];
62cf9924 354 }
e6f2a0fd 355
356 no strict 'refs';
fe63d727 357 no warnings qw/uninitialized/;
62cf9924 358
359 my $cag_slot = '::__cag_'. $_[1];
360 return ${$class.$cag_slot} if defined(${$class.$cag_slot});
e6f2a0fd 361
4f8ce9da 362 # we need to be smarter about recalculation, as @ISA (thus supers) can very well change in-flight
62cf9924 363 my $cur_gen = mro::get_pkg_gen ($class);
364 if ( $cur_gen != ${$class.'::__cag_pkg_gen__'} ) {
365 @{$class.'::__cag_supers__'} = $_[0]->get_super_paths;
366 ${$class.'::__cag_pkg_gen__'} = $cur_gen;
367 }
a49c32d9 368
62cf9924 369 for (@{$class.'::__cag_supers__'}) {
370 return ${$_.$cag_slot} if defined(${$_.$cag_slot});
e6f2a0fd 371 };
c46050d3 372
9c3d5179 373 return undef;
e6f2a0fd 374}
375
376=head2 set_inherited
377
378=over 4
379
380=item Arguments: $field, $new_value
381
382Returns: $new_value
383
384=back
385
331e820d 386Simple setter for Classes and hash-based objects which sets and then returns
387the value for the field name passed as an argument. When called on a hash-based
388object it will set the appropriate hash key value. When called on a class, it
389will set a class level variable.
e6f2a0fd 390
331e820d 391B<Note:>: This method will die if you try to set an object variable on a non
392hash-based object.
e6f2a0fd 393
394=cut
395
396sub set_inherited {
a0bce8bc 397 if (Scalar::Util::blessed $_[0]) {
398 if (Scalar::Util::reftype $_[0] eq 'HASH') {
399 return $_[0]->{$_[1]} = $_[2];
e6f2a0fd 400 } else {
a0bce8bc 401 Carp::croak('Cannot set inherited value on an object instance that is not hash-based');
e6f2a0fd 402 };
403 } else {
404 no strict 'refs';
405
a0bce8bc 406 return ${$_[0].'::__cag_'.$_[1]} = $_[2];
e6f2a0fd 407 };
408}
409
331e820d 410=head2 get_component_class
411
412=over 4
413
414=item Arguments: $field
415
416Returns: $value
417
418=back
419
420Gets the value of the specified component class.
421
422 __PACKAGE__->mk_group_accessors('component_class' => 'result_class');
97d76fb4 423
331e820d 424 $self->result_class->method();
97d76fb4 425
331e820d 426 ## same as
427 $self->get_component_class('result_class')->method();
428
429=cut
430
431sub get_component_class {
a0bce8bc 432 return $_[0]->get_inherited($_[1]);
331e820d 433};
434
435=head2 set_component_class
436
437=over 4
438
439=item Arguments: $field, $class
440
441Returns: $new_value
442
443=back
444
445Inherited accessor that automatically loads the specified class before setting
446it. This method will die if the specified class could not be loaded.
447
448 __PACKAGE__->mk_group_accessors('component_class' => 'result_class');
449 __PACKAGE__->result_class('MyClass');
97d76fb4 450
331e820d 451 $self->result_class->method();
452
453=cut
454
455sub set_component_class {
a0bce8bc 456 if ($_[2]) {
bce7bdf8 457 local $^W = 0;
01249db5 458 require Class::Inspector;
a0bce8bc 459 if (Class::Inspector->installed($_[2]) && !Class::Inspector->loaded($_[2])) {
460 eval "use $_[2]";
331e820d 461
a0bce8bc 462 Carp::croak("Could not load $_[1] '$_[2]': ", $@) if $@;
331e820d 463 };
464 };
465
a0bce8bc 466 return $_[0]->set_inherited($_[1], $_[2]);
331e820d 467};
468
a49c32d9 469=head2 get_super_paths
470
471Returns a list of 'parent' or 'super' class names that the current class inherited from.
472
473=cut
474
475sub get_super_paths {
62cf9924 476 return @{mro::get_linear_isa( ref($_[0]) || $_[0] )};
a49c32d9 477};
478
a2537c55 4791;
b9a69571 480
9d7d52da 481=head1 PERFORMANCE
15cf8e32 482
a2537c55 483You can speed up accessors of type 'simple' by installing L<Class::XSAccessor>.
963a69a5 484
485=head1 AUTHORS
486
487Matt S. Trout <mst@shadowcatsystems.co.uk>
ba6f7b1b 488
97972dcb 489Christopher H. Laco <claco@chrislaco.com>
963a69a5 490
8ef9b3ff 491=head1 CONTRIBUTORS
dfb86526 492
ba6f7b1b 493Caelum: Rafael Kitover <rkitover@cpan.org>
494
8ef9b3ff 495groditi: Guillermo Roditi <groditi@cpan.org>
ba6f7b1b 496
6a4c729f 497Jason Plum <jason.plum@bmmsi.com>
dfb86526 498
ba6f7b1b 499ribasushi: Peter Rabbitson <ribasushi@cpan.org>
500
501
4fe25633 502=head1 COPYRIGHT & LICENSE
963a69a5 503
af169484 504Copyright (c) 2006-2010 Matt S. Trout <mst@shadowcatsystems.co.uk>
963a69a5 505
4fe25633 506This program is free software; you can redistribute it and/or modify
507it under the same terms as perl itself.
963a69a5 508
4fe25633 509=cut