File Coverage

blib/lib/Class/Value.pm
Criterion Covered Total %
statement 75 129 58.1
branch 15 56 26.7
condition 4 19 21.0
subroutine 26 40 65.0
pod 25 25 100.0
total 145 269 53.9


line stmt bran cond sub pod time code
1 1     1   1200 use 5.008;
  1         4  
  1         45  
2 1     1   6 use strict;
  1         1  
  1         40  
3 1     1   5 use warnings;
  1         2  
  1         68  
4              
5             package Class::Value;
6             our $VERSION = '1.100840';
7             # ABSTRACT: Implements the Value Object Design Pattern
8 1     1   1290 use Error ':try';
  1         17120  
  1         7  
9 1     1   844 use Class::Value::DefaultNotify;
  1         4  
  1         15  
10 1     1   925 use Error::Hierarchy::Container;
  1         22456  
  1         16  
11 1     1   51 use parent qw(Class::Accessor::Complex Class::Accessor::Constructor);
  1         2  
  1         6  
12              
13             # Use strings for overloading sub names so they're interpreted as method names
14             # and the methods can be overridden in subclasses while continuing to work as
15             # expected. The overloaded operations have been chosen so that as much as
16             # necessary can be autogenerated (see "perldoc overload": MAGIC
17             # AUTOGENERATION). Subclasses are free to provide custom overloads of
18             # autogenerated methods, of course.
19             use overload
20 1         9 '+' => 'add',
21             '-' => 'subtract',
22             '*' => 'multiply',
23             '/' => 'divide',
24             '**' => 'power',
25             '%' => 'modulo',
26             cmp => 'str_cmp',
27             '<=>' => 'num_cmp',
28             '<<' => 'bit_shift_left',
29             '>>' => 'bit_shift_right',
30             '&' => 'bit_and',
31             '|' => 'bit_or',
32             '^' => 'bit_xor',
33             '~' => 'bit_not',
34             'atan2' => 'atan2',
35             'cos' => 'cos',
36             'sin' => 'sin',
37             'exp' => 'exp',
38             'log' => 'log',
39             'sqrt' => 'sqrt',
40             'int' => 'int',
41             '""' => 'stringify',
42 1     1   2851 '<>' => 'iterate';
  1         2  
43             __PACKAGE__
44             ->mk_constructor
45             ->mk_scalar_accessors(qw(notify_delegate))
46             ->mk_object_accessors(
47             'Error::Hierarchy::Container' => 'exception_container'
48             );
49 1     1   387 use constant UNHYGIENIC => (qw/value/);
  1         2  
  1         278  
50              
51             # our(), not a static boolean method, so it can be local()'ed.
52             our $SkipChecks = 1;
53             our $SkipNormalizations = 0;
54             our $ThrowSingleException = 0;
55              
56             sub skip_checks {
57 0     0 1 0 our $SkipChecks;
58 0 0       0 return $SkipChecks if @_ == 1;
59 0         0 $SkipChecks = $_[1];
60             }
61              
62             sub skip_normalizations {
63 0     0 1 0 our $SkipNormalizations;
64 0 0       0 return $SkipNormalizations if @_ == 1;
65 0         0 $SkipNormalizations = $_[1];
66             }
67              
68             sub throw_single_exception {
69 0     0 1 0 our $ThrowSingleException;
70 0 0       0 return $ThrowSingleException if @_ == 1;
71 0         0 $ThrowSingleException = $_[1];
72             }
73              
74             # Every value object gets the same notify delegate object
75 1   33     27 use constant DEFAULTS =>
76 1     1   8 (notify_delegate => (our $DELEGATE ||= Class::Value::DefaultNotify->new),);
  1         1  
77 1     1   78 use constant FIRST_CONSTRUCTOR_ARGS => ('notify_delegate');
  1         2  
  1         1300  
78              
79             sub MUNGE_CONSTRUCTOR_ARGS {
80 3     3 1 1645 my $self = shift;
81              
82             # if (@_ == 1 && ref($_[0]) eq 'HASH') {
83             # @_ = %{ $_[0] };
84             # } elsif (@_ % 2) {
85 3 50       16 if (@_ % 2) {
86              
87             # odd number of args
88 0         0 unshift @_, 'value';
89             }
90 3         7 my $ref = ref $self;
91 3         6 our %cache_isa;
92              
93             # cache hash value is 0 or 1, so need to use exists()
94 3 100       13 unless (exists $cache_isa{$ref}) {
95 1         8 $cache_isa{$ref} = UNIVERSAL::isa($self, 'Class::Scaffold::Storable');
96             }
97 3 50       9 if ($cache_isa{$ref}) {
98 0         0 return $self->Class::Scaffold::Storable::MUNGE_CONSTRUCTOR_ARGS(@_);
99             }
100 3         16 @_;
101             }
102 3     3 1 42 sub init { }
103              
104             sub value {
105 118     118 1 3390 my $self = shift;
106 118 100       418 return $self->get_value unless @_;
107 3         6 my $value = shift;
108              
109             # run_checks() returns normalized value; check even undefined values -
110             # individual value objects have to decide whether undef is an acceptable
111             # value for them.
112 3 50       9 if (our $SkipChecks) {
113              
114 3         5 our $SkipNormalizations;
115 3 50 33     21 if (defined($value) && !$SkipNormalizations) {
116 3         10 my $normalized = $self->normalize_value($value);
117 3 50       10 $value = $normalized if defined $normalized;
118             }
119             } else {
120 0         0 $value = $self->run_checks($value);
121             }
122 3         11 $self->set_value($value);
123              
124 3         9 $value; # return for convenience
125             }
126              
127             # Subclasses might want to override this if they don't use a custom notify
128             # delegate but choose to throw a fixed exception.
129             sub send_notify_value_not_wellformed {
130 0     0 1 0 my ($self, $value) = @_;
131 0         0 $self->notify_delegate->notify_value_not_wellformed(ref($self), $value);
132             }
133              
134             sub send_notify_value_invalid {
135 0     0 1 0 my ($self, $value) = @_;
136 0         0 $self->notify_delegate->notify_value_invalid(ref($self), $value);
137             }
138              
139             sub send_notify_value_normalized {
140 0     0 1 0 my ($self, $value, $normalized) = @_;
141 0         0 $self->notify_delegate->notify_value_normalized(ref($self), $value,
142             $normalized);
143             }
144 115     115 1 806 sub get_value { $_[0]->{_value} }
145 3     3 1 9 sub set_value { $_[0]->{_value} = $_[1] }
146              
147             sub is_defined {
148 0     0 1 0 my $self = shift;
149 0         0 defined $self->get_value;
150             }
151              
152             sub is_well_formed {
153 2     2 1 595 my $self = shift;
154 2 100       10 $self->is_well_formed_value(@_ ? shift : $self->value);
155             }
156 2     2 1 16 sub is_well_formed_value { 1 }
157              
158             sub is_valid {
159 1     1 1 3 my $self = shift;
160 1 50       6 $self->is_valid_value(@_ ? shift : $self->value);
161             }
162              
163             sub is_valid_value {
164 1     1 1 3 my ($self, $value) = @_;
165              
166             # value can be undef
167 1 50       5 return 1 unless defined $value;
168 1         4 my $normalized = $self->normalize_value($value);
169 1 50       5 return 0 unless defined $normalized;
170 1         4 $self->is_valid_normalized_value($normalized);
171             }
172              
173             sub is_valid_normalized_value {
174 1     1 1 3 my ($self, $normalized) = @_;
175 1 50       7 defined $normalized && $self->is_well_formed($normalized);
176             }
177              
178             sub normalize_value {
179 4     4 1 8 my ($self, $value) = @_;
180 4         9 $value;
181             }
182              
183             sub check {
184 0     0 1 0 my $self = shift;
185 0 0       0 my $value = @_ ? shift : $self->value;
186 0 0       0 $self->is_well_formed($value) && $self->is_valid($value);
187             }
188              
189             sub run_checks {
190 0     0 1 0 my $self = shift;
191 0         0 $self->exception_container->items_clear;
192 0 0       0 my $value = @_ ? shift : $self->value;
193 0 0       0 $self->is_well_formed($value)
194             || $self->send_notify_value_not_wellformed($value);
195 0 0       0 $self->is_valid($value) || $self->send_notify_value_invalid($value);
196 0         0 my $normalized = $self->normalize($value);
197 0 0 0     0 if (defined($value) && defined($normalized) && ($value ne $normalized)) {
      0        
198 0         0 $self->send_notify_value_normalized($value, $normalized);
199             }
200 0 0       0 if (my $count = $self->exception_container->items_count) {
201 0 0 0     0 if ($count == 1 && our $ThrowSingleException) {
202 0         0 $self->exception_container->items->[0]->throw;
203             } else {
204 0         0 $self->exception_container->throw;
205             }
206             }
207 0         0 $normalized;
208             }
209              
210             sub run_checks_with_exception_container {
211 0     0 1 0 my $self = shift;
212 0         0 my $exception_container = shift;
213 0 0       0 my $value = @_ ? shift : $self->value;
214             try {
215 0     0   0 $self->run_checks($value);
216             }
217             catch Error with {
218 0     0   0 $exception_container->items_set_push($_[0]);
219 0         0 };
220              
221             # We only needed to fill the value object's exception container during
222             # run_checks; now the exceptions have wandered into the exception
223             # container that was passed to us, we don't need the value object's
224             # exception container anymore.
225 0         0 $self->exception_container->items_clear;
226             }
227              
228             sub normalize {
229 0     0 1 0 my ($self, $value) = @_;
230 0         0 my $normalized = $self->normalize_value($value);
231 0 0       0 if (defined $value) {
232 0 0       0 if (defined $normalized) {
233 0 0       0 if ($value ne $normalized) {
234 0         0 $self->send_notify_value_normalized($value, $normalized);
235             }
236             } else {
237              
238             # can't normalize; treat as invalid value
239 0         0 $self->send_notify_value_invalid($value);
240             }
241             }
242 0         0 $normalized;
243             }
244              
245             # have the Class::Value be restrictive with respect to operations on the
246             # value; subclasses can then define certain operations.
247             for my $op (
248             qw/add subtract multiply divide power modulo num_cmp
249             bit_shift_left bit_shift_right bit_and bit_or bit_xor bit_not
250             atan2 cos sin exp log sqrt int iterate
251             /
252             ) {
253 1     1   6 no strict 'refs';
  1         3  
  1         219  
254             *{$op} = sub {
255 38     38   37330 require Class::Value::Exception::UnsupportedOperation;
256 38         241 throw Class::Value::Exception::UnsupportedOperation(
257             ref => ref($_[0]),
258             value => $_[0],
259             opname => $op,
260             );
261             };
262             }
263              
264             sub str_cmp {
265 9   50 9 1 35 sprintf("%s", ($_[0] || '')) cmp sprintf("%s", ($_[1] || ''));
      50        
266             }
267 113     113 1 110346 sub stringify { $_[0]->value }
268              
269             sub comparable {
270 0     0 1   my $self = shift;
271 0           my $value = $self->value;
272              
273             # Convert the value into a string, because eq_or_diff seems to make a
274             # difference between strings and numbers.
275 0 0         defined $value ? "$value" : '';
276             }
277             1;
278              
279              
280             __END__