File Coverage

blib/lib/POOF/DataType.pm
Criterion Covered Total %
statement 126 167 75.4
branch 51 128 39.8
condition 31 65 47.6
subroutine 32 47 68.0
pod 0 23 0.0
total 240 430 55.8


line stmt bran cond sub pod time code
1             package POOF::DataType;
2              
3 5     5   1333 use 5.007;
  5         17  
  5         198  
4 5     5   27 use strict;
  5         9  
  5         160  
5 5     5   37 use warnings;
  5         9  
  5         207  
6 5     5   130 use Carp;
  5         14  
  5         416  
7 5     5   8981 use Class::ISA;
  5         5513  
  5         134  
8              
9 5     5   30 use Scalar::Util 'refaddr';
  5         8  
  5         932  
10              
11             our $VERSION = '1.0';
12              
13             #-------------------------------------------------------------------------------
14              
15             # perl data types
16 5     5   27 use constant SCALAR_REF => 'SCALAR';
  5         9  
  5         347  
17 5     5   23 use constant ARRAY_REF => 'ARRAY';
  5         7  
  5         233  
18 5     5   23 use constant HASH_REF => 'HASH';
  5         8  
  5         400  
19 5     5   24 use constant SOCKET_REF => 'SOCKET';
  5         8  
  5         242  
20 5     5   25 use constant PIPE_REF => 'PIPE';
  5         23  
  5         369  
21 5     5   28 use constant HANDLE_REF => 'HANDLE';
  5         9  
  5         231  
22 5     5   24 use constant GLOB_REF => 'GLOB';
  5         19  
  5         246  
23 5     5   24 use constant CODE_REF => 'CODE';
  5         8  
  5         203  
24              
25             # Multi-threading
26 5     5   21 use constant THREADSAFE => 'threadsafe';
  5         8  
  5         3095  
27              
28             # data primitives
29 5         3385 use constant DATATYPES =>
30             {
31             integer =>
32             {
33             'type' => 'integer',
34             'regex' => qr/^-?[0-9]{1,15}$/,
35             'orm' => 0,
36             'null' => 0,
37             'default' => 0,
38             },
39             numeric =>
40             {
41             'type' => 'string',
42             'regex' => qr/^[0-9]{1,255}$/,
43             'orm' => 0,
44             'null' => 0,
45             'default' => 0,
46             },
47             string =>
48             {
49             'type' => 'string',
50             'orm' => 0,
51             'null' => 0,
52             'default' => '',
53             },
54             char =>
55             {
56             'type' => 'string',
57             'orm' => 0,
58             'null' => 0,
59             'size' => 1,
60             'default' => '',
61             },
62             binary =>
63             {
64             'type' => 'string',
65             'orm' => 0,
66             'null' => 0,
67             'default' => '',
68             },
69             double =>
70             {
71             'type' => 'string',
72             'orm' => 0,
73             'null' => 0,
74             'default' => '',
75             },
76             float =>
77             {
78             'type' => 'string',
79             'regex' => qr/^(?:[0-9]{1,11}|[0-9]{0,11}\.[0-9]{1,11})$/,
80             'orm' => 0,
81             'null' => 0,
82             'default' => '0.0',
83             },
84             long =>
85             {
86             'type' => 'string',
87             'orm' => 0,
88             'null' => 0,
89             'default' => '',
90             },
91             boolean =>
92             {
93             'type' => 'integer',
94             'orm' => 0,
95             'null' => 0,
96             'default' => 0,
97             'size' => 1,
98             'min' => 0,
99             'max' => 1,
100             },
101             blob =>
102             {
103             'type' => 'blob',
104             'orm' => 0,
105             'null' => 1,
106             'default' => undef,
107             },
108             hash =>
109             {
110             'type' => 'hash',
111             'orm' => 0,
112             'null' => 0,
113             'default' => {},
114             'ptype' => HASH_REF
115             },
116             array =>
117             {
118             'type' => 'array',
119             'orm' => 0,
120             'null' => 0,
121             'default' => [],
122             'ptype' => ARRAY_REF
123             },
124             enum =>
125             {
126             'type' => 'string',
127             'orm' => 0,
128             'null' => 1,
129             'default' => undef,
130             'options' => [],
131             },
132             code =>
133             {
134             'type' => 'code',
135             'orm' => 0,
136             'null' => 1,
137             'ptype' => CODE_REF
138             }
139 5     5   32 };
  5         11  
140              
141 5         14751 use constant PROPERTIES =>
142             {
143             name => DATATYPES->{'string'},
144             otype => DATATYPES->{'string'},
145             ptype => DATATYPES->{'string'},
146             type => DATATYPES->{'string'},
147             regex => DATATYPES->{'string'},
148             orm => DATATYPES->{'boolean'},
149             null => DATATYPES->{'boolean'},
150             default => DATATYPES->{'string'},
151             size => DATATYPES->{'integer'},
152             minsize => DATATYPES->{'integer'},
153             maxsize => DATATYPES->{'integer'},
154             precision => DATATYPES->{'integer'},
155             min => DATATYPES->{'float'},
156             max => DATATYPES->{'float'},
157             format => DATATYPES->{'string'},
158             options => DATATYPES->{'array'},
159             ifilter => DATATYPES->{'code'},
160             ofilter => DATATYPES->{'code'},
161 5     5   40 };
  5         9  
162              
163             # class encapsulation core
164             my $core;
165             my $errors;
166              
167             sub new
168             {
169 23     23 0 2287 my ($class, $args) = @_;
170 23         42 my $obj = { };
171 23         48 bless $obj, $class;
172            
173 23         50 $obj->_init( $args );
174            
175 23         171 return $obj;
176             }
177              
178 282     282   1220 sub _objectInstanceID { refaddr( $_[0] ) }
179              
180             sub _init
181             {
182 23     23   33 my ($obj,$args) = @_;
183 23         42 my $oid = $obj->_objectInstanceID;
184              
185             # If we are supplied a hashref as arguments to the constructions let's
186             # populate the object's core hash with those properties
187 23 50 33     233 if (ref($args) eq HASH_REF && exists $args->{'type'} && $args->{'type'})
      33        
188             {
189             # if a dtype property matches a default data type, let's prepopulate
190             # with the default values and then apply the custom values supplied
191             # with the args.
192 23         170 (%{$core->{ $oid }}) =
  0         0  
193             exists DATATYPES->{ $args->{'type'} }
194 23         88 ? (defined $core->{ $oid } ? %{$core->{ $oid }} : (), %{ +DATATYPES->{ $args->{'type'} } }, %{$args})
  23         62  
  0         0  
195 23 50       97 : (defined $core->{ $oid } ? %{$core->{ $oid }} : (), %{$args});
  0 0       0  
    50          
196            
197 23         58 return $args;
198             }
199             else
200             {
201 0         0 croak "Cowardly refused to instantiate a data type without a type definition\n";
202             }
203             }
204              
205 0 0   0 0 0 sub name { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
206 78 100   78 0 7434 sub value { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
207 0 0   0 0 0 sub type { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
208 0 0   0 0 0 sub ptype { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
209 0 0   0 0 0 sub otype { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
210 0 0   0 0 0 sub regex { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
211 0 0   0 0 0 sub orm { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
212 0 0   0 0 0 sub null { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
213 2 50   2 0 526 sub default { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
214 0 0   0 0 0 sub size { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
215 0 0   0 0 0 sub minsize { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
216 0 0   0 0 0 sub maxsize { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
217 0 0   0 0 0 sub min { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
218 0 0   0 0 0 sub max { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
219 0 0   0 0 0 sub format { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
220 0 0   0 0 0 sub options { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
221 37 50   37 0 114 sub ifilter { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
222 75 50   75 0 225 sub ofilter { @_ == 2 ? $_[0]->Property( $_[1] ) : $_[0]->Property }
223              
224              
225             sub Property
226             {
227 192     192 0 420 $_[0]->Private;
228 192         470 my $obj = shift;
229 192         261 my ($dat) = @_;
230            
231             # gathering info on how the caller was called
232 192         1103 my ($package,$method,$args,$wantarray) = (caller(1))[0,3,4,5];
233              
234             # extract property name or bail
235 192 50       4458 my $property =
236             $method =~ /::([^:]+)$/o
237             ? $1
238             : croak "Can't determine the property name\n";
239            
240 192 100       347 if (defined $wantarray)
241             {
242 170 100       546 $obj->_setValue($property,$dat) if @_;
243             # we must get the property value
244 170         364 return $obj->_getValue($property);
245             }
246             else
247             {
248             # property was called in void context, lets set its value if provided
249 22         56 return $obj->_setValue($property,$dat);
250             }
251             }
252              
253             sub _setValue
254             {
255 27     27   55 $_[0]->Private;
256 27         80 my ($obj,$property,$dat) = @_;
257            
258 27         55 my $oid = $obj->_objectInstanceID;
259            
260             # let's validate against the property definition
261 27 100       73 if ($obj->_valid($property,$dat))
262             {
263 23         60 $core->{ $oid }->{ $property } = $dat;
264 23 50       68 delete $errors->{ $oid }->{ $property } if exists $errors->{ $oid }->{ $property };
265 23         76 return $obj;
266             }
267             # if we made it here is bacause validation failed and we are
268             # returning undef because we are not in a void context
269             # the caller should check the $obj->pGetErrors to see the actual
270             # error message.
271 4         13 return;
272             }
273              
274             sub _getValue
275             {
276 171     171   776 $_[0]->Private;
277 170         648 my ($obj,$property) = @_;
278 170         491 my $oid = $obj->_objectInstanceID;
279            
280             return
281 170 50       2512 defined $core->{ $oid }->{ $property }
    50          
    100          
282             ? defined $core->{ $oid }->{'format'}
283             ? sprintf $core->{ $oid }->{'format'}, $core->{ $oid }->{ $property }
284             : $core->{ $oid }->{ $property }
285             : defined $core->{ $oid }->{'format'}
286             ? sprintf $core->{ $oid }->{'format'}, $core->{ $oid }->{'default'}
287             : $core->{ $oid }->{'default'};
288             }
289              
290             sub _valid
291             {
292 27     27   50 $_[0]->Private;
293 27         91 my ($obj,$property,$dat) = @_;
294 27         76 my $oid = $obj->_objectInstanceID;
295              
296 27 50 66     124 my $definition =
297             $property eq 'value' || $property eq 'default'
298             ? $core->{ $oid }
299             : PROPERTIES->{$property};
300            
301              
302              
303             # check null
304 27 50 33     151 if (exists $definition->{'null'} && defined $definition->{'null'})
305             {
306 27 100       65 unless(defined $dat)
307             {
308             # if it can be null and it is null just return 1
309 3 50       16 return 1 if $definition->{'null'} == 1;
310              
311             # otherwise, complain that is null and return undef
312 0 0       0 $errors->{ $oid }->{ $property } =
313             {
314             'code' => 111,
315             'description' => 'NULL test failed',
316             'value' => defined $dat ? $dat : undef
317             };
318 0         0 return;
319             }
320             }
321            
322             # check type
323 24 50 33     557 if
      33        
      33        
      33        
      33        
324             (
325             (
326             exists $definition->{'type'}
327             && !(exists +DATATYPES->{ $definition->{'type'} })
328             && defined $dat
329             && $obj->_Relationship(ref($dat),$definition->{'type'}) !~ /^(?:self|child)$/
330             )
331             or
332             (
333             exists $definition->{'ptype'}
334             && ref($dat) ne $definition->{'ptype'}
335             )
336             )
337             {
338 0 0       0 $errors->{ $oid }->{ $property } =
339             {
340             'code' => 101,
341             'description' => 'type test failed',
342             'value' => defined $dat ? $dat : undef
343             };
344 0         0 return;
345             }
346            
347             # check enum
348 24 100 66     138 if (defined $dat && $definition->{'type'} eq 'enum')
349             {
350 3 100       5 if (grep { $_ eq $dat } @{$definition->{'options'}})
  9         24  
  3         5  
351             {
352 2         7 return 1;
353             }
354             else
355             {
356 1 50       8 $errors->{ $oid }->{ $property } =
357             {
358             'code' => 151,
359             'description' => 'Not a valid options for this enumerated property',
360             'value' => defined $dat ? $dat : undef
361             };
362 1         5 return;
363             }
364             }
365            
366             # check regex
367 21 100 66     93 if (exists $definition->{'regex'} && defined $definition->{'regex'})
368             {
369 12 100       112 unless($dat =~ $definition->{'regex'})
370             {
371 2 50       188 $errors->{ $oid }->{ $property } =
372             {
373             'code' => 121,
374             'description' => 'regex test failed',
375             'value' => defined $dat ? $dat : undef
376             };
377 2         10 return;
378             }
379             }
380            
381             # check size
382 19 100 66     69 if (exists $definition->{'size'} && defined $definition->{'size'})
383             {
384 2 50 50     10 unless(length($dat) <= ($definition->{'size'} || 0) )
385             {
386 0 0       0 $errors->{ $oid }->{ $property } =
387             {
388             'code' => 131,
389             'description' => 'size test failed',
390             'value' => defined $dat ? $dat : undef
391             };
392 0         0 return;
393             }
394             }
395             # check min size
396 19 50 33     68 if (exists $definition->{'minsize'} && defined $definition->{'minsize'})
397             {
398 0 0 0     0 unless(length($dat) >= ($definition->{'minsize'} || 0) )
399             {
400 0 0       0 $errors->{ $oid }->{ $property } =
401             {
402             'code' => 132,
403             'description' => 'minsize test failed',
404             'value' => defined $dat ? $dat : undef
405             };
406 0         0 return;
407             }
408             }
409            
410             # check max size
411 19 50 33     120 if (exists $definition->{'maxsize'} && defined $definition->{'maxsize'})
412             {
413 0 0 0     0 unless(length($dat) <= ($definition->{'maxsize'} || 0) )
414             {
415 0 0       0 $errors->{ $oid }->{ $property } =
416             {
417             'code' => 133,
418             'description' => 'maxsize test failed',
419             'value' => defined $dat ? $dat : undef
420             };
421 0         0 return;
422             }
423             }
424            
425             # check min
426 19 100 66     91 if (exists $definition->{'min'} && defined $definition->{'min'})
427             {
428 3 50 100     19 unless($dat >= ($definition->{'min'} || 0) )
429             {
430 0 0       0 $errors->{ $oid }->{ $property } =
431             {
432             'code' => 141,
433             'description' => 'Min test failed',
434             'value' => defined $dat ? $dat : undef
435             };
436 0         0 return;
437             }
438             }
439            
440             # check max
441 19 100 66     66 if (exists $definition->{'max'} && defined $definition->{'max'})
442             {
443 2 100 50     11 unless($dat <= ($definition->{'max'} || 0) )
444             {
445 1 50       9 $errors->{ $oid }->{ $property } =
446             {
447             'code' => 142,
448             'description' => 'Max test failed',
449             'value' => defined $dat ? $dat : undef
450             };
451 1         5 return;
452             }
453             }
454            
455 18         53 return 1;
456             }
457              
458             sub Private
459             {
460 417 100 66 417 0 6097 croak "Illegal access of a private method\n"
461             unless ((caller(0))[0] eq ref($_[0])) && ((caller(1))[0] eq ref($_[0]));
462             }
463              
464             sub pErrors
465             {
466 25     25 0 1018 my ($obj) = @_;
467 25   100     39 return @{ [ keys %{$errors->{ $obj->_objectInstanceID }} ] } || 0;
468             }
469              
470             sub pGetErrors
471             {
472 2     2 0 4 my ($obj) = @_;
473 2   50     6 return $errors->{ $obj->_objectInstanceID } || { };
474             }
475              
476             sub _Relationship
477             {
478 0     0   0 my $obj = shift;
479 0 0       0 my ($class1,$class2) = map { $_ ? ref $_ ? ref $_ : $_ : '' } @_;
  0 0       0  
480              
481 0 0       0 return 'self' if $class1 eq $class2;
482              
483 0         0 my %family1 = map { $_ => 1 } Class::ISA::super_path( $class1 );
  0         0  
484 0         0 my %family2 = map { $_ => 1 } Class::ISA::super_path( $class2 );
  0         0  
485              
486             return
487 0 0       0 exists $family1{ $class2 }
    0          
488             ? 'child'
489             : exists $family2{ $class1 }
490             ? 'parent'
491             : 'unrelated';
492             }
493              
494             # we must cleanup and force this instance to undef
495             sub DESTROY
496             {
497 8     8   4154 delete $core->{ $_[0]->_objectInstanceID };
498             }
499              
500             1;
501             __END__