File Coverage

blib/lib/Fukurama/Class/DataTypes.pm
Criterion Covered Total %
statement 45 54 83.3
branch 15 24 62.5
condition 4 9 44.4
subroutine 7 8 87.5
pod 5 5 100.0
total 76 100 76.0


line stmt bran cond sub pod time code
1             package Fukurama::Class::DataTypes;
2 5     5   23865 use Fukurama::Class::Version(0.04);
  5         12  
  5         33  
3 5     5   26 use Fukurama::Class::Rigid;
  5         10  
  5         25  
4              
5             =head1 NAME
6              
7             Fukurama::Class::DataTypes - Helper class to register and check datatypes
8              
9             =head1 VERSION
10              
11             Version 0.04 (beta)
12              
13             =head1 SYNOPSIS
14              
15             package MyClass;
16              
17             BEGIN {
18             use Fukurama::Class::DataTypes();
19             Fukurama::Class::DataTypes->set_type_checker('MyOwnClass', sub {
20             my $parameter = $_[0];
21             my $data_type_checker_name = $_[1];
22            
23             my $error = undef;
24             my $is_ok = 0;
25             if(ref($parameter) && UNIVERSAL::isa($parameter, 'MyOwnClass')) {
26             if($parameter->get('name') eq 'MyOwnName') {
27             $is_ok = 1;
28             } else {
29             $error = 'nameIsNotCorrect';
30             }
31             } else {
32             $error = 'notAnObject';
33             }
34             return ($is_ok, $parameter, $error);
35             });
36             }
37             use Fukurama::Class;
38            
39             # Croak, if parameter one is not an instance of 'MyOwnClass'
40             # and doesn't have the name 'MyOwnName'
41             sub set_my_own_class : Method(public|MyOwnClass|boolean) {
42             ...
43             }
44              
45             =head1 DESCRIPTION
46              
47             This helper-class provides functions to register and handle check-methods for several data types.
48              
49             =head1 EXPORT
50              
51             -
52              
53             =head1 METHODS
54              
55             =over 4
56              
57             =item set_type_checker( name:STRING, checker:CODE ) return:BOOLEAN
58              
59             Set a check-method for a new or an existing datatype. B is the identifier string for the data type,
60             B is a code reference to check the data type.
61              
62             Examples for B: I , I, I
63             Native data types have to be in lowercase. Object data types have to start with an uppercase letter.
64             You should be careful when you define the identifier because object data types would be handeled different as
65             native data types.
66              
67             An example for a type checker for a simple hash reference:
68              
69             $class->set_type_checker('hashref', sub {
70             my $parameter = $_[0];
71             my $data_type_checker_name = $_[1];
72            
73             my $error = undef;
74             my $is_ok = 0;
75             if(ref($parameter) eq 'HASH') {
76             $is_ok = 1;
77             } else {
78             $error = 'notARef';
79             }
80             return ($is_ok, $parameter, $error);
81             });
82              
83             =item set_ref_checker( identifier:STRING, checker:CODE ) return:BOOLEAN
84              
85             Set a check-method for a new or an existing reference type. B is the string which identifies this
86             reference, B is a code referende to check the reference type.
87              
88             Examples for B: I<[]> (for array reference), I<{}> (for hash reference)
89              
90             An example for a reference checker for array references:
91              
92             $class->set_ref_checker('[]' => sub {
93             my $data_type_checker = $_[0];
94             my $parameter = $_[1];
95             my $data_type_checker_name = $_[2];
96             my $actual_subroutine_parameter = $_[3]; # \INT
97             my $full_subroutine_parameter_list = $_[4]; # \ARRAY
98            
99             return 0 if(ref($parameter) ne 'ARRAY');
100             my $i = 0;
101             my $error = undef;
102             # Check all entries of this array reference
103             foreach my $parameter_entry (@{$parameter}) {
104             my ($is_ok, $returned_parameter, $returned_error) = &{$data_type_checker}($parameter_entry, $data_type_checker_name);
105             if(!$is_ok) {
106             $parameter->[$i] = $returned_parameter;
107             $error = [ $is_ok, $parameter, $returned_error ];
108             }
109             ++$i;
110             }
111             return @$error if($error) {
112             1;
113             });
114              
115             =item is_ref_allowed( identifier:STRING ) return:BOOLEAN
116              
117             Method to check if the given identifier has a defined reference checker.
118              
119             =item get_check_definition ( type_name:STRING, ref_identifier:STRING ) return:HASHREF
120              
121             For internal usage in attribute helper classes. Get the defined checker
122             methods for data type and the reference identifier as a hash reference.
123              
124             {
125             is_class => data_type_is_a_class:BOOLEAN,
126             check => reference_checker:CODE,
127             param_0 => data_type_checker:CODE,
128             }
129              
130             =item check_parameter_definition ( type_name:STRING, parameter_definition:HASHREF ) return:BOOLEAN
131              
132             For internal usage in attribute helper classes. Check the given parameter definition.
133              
134             =back
135              
136             =head1 AUTHOR, BUGS, SUPPORT, ACKNOWLEDGEMENTS, COPYRIGHT & LICENSE
137              
138             see perldoc of L
139              
140             =cut
141              
142             my $OVERFLOW_SIGN;
143             BEGIN {
144 5     5   13 my $i = 0;
145 5         10 my $float;
146 5         33 while(++$i) {
147 20         44 $float = '1.2e+' . ($i * 100);
148 20         49 my $result = ($float * 1) - $float;
149 20 100       65 if($result ne '0') {
150 5         17 $OVERFLOW_SIGN = $float * 1;
151 5         10125 last;
152             }
153 15 50       53 if($i > 1_000) {
154 0         0 $OVERFLOW_SIGN = 'inf';
155 0         0 last;
156             }
157             }
158             }
159             my $HAS_OVERFLOW = sub {
160             ($_[0] * 1) eq $OVERFLOW_SIGN;
161             };
162             # param: value:SCALAR, type:STRING
163             my $TYPES = {
164             void => sub {
165             return 1 if(!defined($_[0]));
166             (0, $_[0]);
167             },
168             scalar => sub {
169             1
170             },
171             scalarref => sub {
172             return 1 if(ref($_[0]) eq 'SCALAR');
173             (0, $_[0]);
174             },
175             arrayref => sub {
176             return 1 if(ref($_[0]) eq 'ARRAY');
177             (0, $_[0]);
178             },
179             hashref => sub {
180             return 1 if(ref($_[0]) eq 'HASH');
181             (0, $_[0]);
182             },
183             typglobref => sub {
184             return 1 if(ref($_[0]) eq 'GLOB');
185             (0, $_[0]);
186             },
187             string => sub {
188             return 1 if(defined($_[0]) && !ref($_[0]));
189             (0, $_[0]);
190             },
191             boolean => sub {
192             return 1 if(defined($_[0]) && ($_[0] eq '0' || $_[0] eq '1'));
193             (0, $_[0]);
194             },
195             int => sub {
196             return 1 if(defined($_[0]) && $_[0] =~ m/^\-?[0-9]+$/ && ($_[0] * 1) eq $_[0]);
197             return (0, $_[0]) if(!defined($_[0]));
198             return (0, $_[0], 'noInt') if($_[0] !~ m/^\-?[0-9]+$/);
199             return (0, $_[0] * 1, 'overflow') if(&$HAS_OVERFLOW($_[0]) || ($_[0] * 1) ne $_[0]);
200             (0, $_[0] * 1);
201             },
202             float => sub {
203             return 1 if(
204             defined($_[0])
205             && ( $_[0] =~ m/^[0-9]+\.?[0-9]*$/ || $_[0] =~ m/^[0-9]+\.?[0-9]*e\+?[0-9]+/)
206             && ($_[0] * 1) == $_[0]
207             && !&$HAS_OVERFLOW($_[0])
208             );
209             return (0, $_[0]) if(!defined($_[0]));
210             return (0, $_[0], 'NaN') if($_[0] !~ m/^[0-9]+\.?[0-9]*$/ && $_[0] !~ m/^[0-9]+\.?[0-9]*e\+?[0-9]+$/);
211             return (0, $_[0] * 1, 'overflow') if(&$HAS_OVERFLOW($_[0]) || ($_[0] * 1) != $_[0]);
212             (0, $_[0]);
213             },
214             decimal => sub {
215             return 1 if(defined($_[0]) && $_[0] =~ m/^\-?[0-9]+\.?[0-9]*$/ && ($_[0] * 1) eq $_[0]);
216             return (0, $_[0]) if(!defined($_[0]));
217             return (0, $_[0], 'NaN') if($_[0] !~ m/^[0-9]+\.?[0-9]*$/ && $_[0] !~ m/^[0-9]+\.?[0-9]*e\+?[0-9]+$/);
218             return (0, $_[0] * 1, 'overflow') if(&$HAS_OVERFLOW($_[0]) || ($_[0] * 1) ne $_[0]);
219             return (0, $_[0], 'noDec') if($_[0] !~ m/^\-?[0-9]+\.?[0-9]*$/);
220             (0, $_[0] * 1);
221             },
222             class => sub {
223             return 1 if(!ref($_[0]) && UNIVERSAL::isa($_[0], $_[0]));
224             (0, $_[0]);
225             },
226             object => sub {
227             return 1 if(ref($_[0]) && UNIVERSAL::isa($_[0], ref($_[0])));
228             (0, $_[0]);
229             },
230             '*class*' => sub {
231             return 1 if(ref($_[0]) && UNIVERSAL::isa($_[0], $_[1]));
232             (0, $_[0]);
233             },
234             };
235             my $CLASS_TYPES = {
236             class => 1,
237             object => 1,
238             };
239             # param: check_sub:CODE, value:SCALAR, type:STRING, pos:\INT, all_io:\ARRAY
240             my $REFS = {
241             '' => sub {
242             &{$_[0]}($_[1], $_[2]);
243             },
244             '[]' => sub {
245             return 0 if(ref($_[1]) ne 'ARRAY');
246             my $i = 0;
247             my $error = undef;
248             foreach my $entry (@{$_[1]}) {
249             my @result = &{$_[0]}($entry, $_[2]);
250             if(!$result[0]) {
251             $_[1]->[$i] = $result[1];
252             $error = \@result;
253             }
254             ++$i;
255             }
256             if($error) {
257             $error->[1] = $_[1];
258             return @$error;
259             }
260             1;
261             },
262             '()' => sub {
263             my $error = undef;
264             my @io = @{$_[4]}[${$_[3]}..$#{$_[4]}];
265             foreach my $entry (@io) {
266             my @result = &{$_[0]}($entry, $_[2]);
267             if(!$result[0]) {
268             $error = \@result;
269             last;
270             }
271             }
272             ${$_[3]} = $#{$_[4]};
273             return @$error if($error);
274             1;
275             },
276             '{}' => sub {
277             return 0 if(ref($_[1]) ne 'HASH');
278             my $error = undef;
279             foreach my $key (keys(%{$_[1]})) {
280             my $entry = $_[1]->{$key};
281             my @result = &{$_[0]}($entry, $_[2]);
282             if(!$result[0]) {
283             $_[1]->{$key} = $result[1];
284             $error = \@result;
285             }
286             }
287             if($error) {
288             $error->[1] = $_[1];
289             return @$error;
290             }
291             1;
292             }
293             };
294             # boolean
295             sub set_ref_checker {
296 0     0 1 0 my $class = $_[0];
297 0         0 my $identifier = $_[1];
298 0         0 my $code = $_[2];
299            
300 0 0 0     0 return 0 if(!length($identifier) || ref($code) ne 'CODE');
301 0         0 $REFS->{$identifier} = $code;
302 0         0 return 1;
303             }
304             # boolean
305             sub set_type_checker {
306 1     1 1 21 my $class = $_[0];
307 1         3 my $identifier = $_[1];
308            
309 1         2 my $code = $_[2];
310            
311 1 50 33     9 return 0 if(!length($identifier) || ref($code) ne 'CODE');
312 1         3 $TYPES->{$identifier} = $code;
313 1         3 return 1;
314             }
315             # boolean
316             sub is_ref_allowed {
317 54     54 1 67 my $class = $_[0];
318 54         83 my $identifier = $_[1];
319            
320 54 50       238 return 1 if(exists($REFS->{$identifier}));
321 0         0 return 0;
322             }
323             # hashref
324             sub get_check_definition {
325 139     139 1 112415 my $class = $_[0];
326 139         171 my $type = $_[1];
327 139         188 my $ref = $_[2];
328            
329 139         251 my $ref_sub = $REFS->{$ref};
330 139 50       328 return {} if(!$ref_sub);
331            
332 139         177 my $is_class = 0;
333 139         224 my $type_sub = $TYPES->{$type};
334 139 100       286 if(!$type_sub) {
335 15 50       62 return {} if($type !~ /^[A-Z]/);
336 15         30 $type_sub = $TYPES->{'*class*'};
337 15         30 $is_class = 1;
338             }
339 139 100 100     759 $is_class = 1 if($CLASS_TYPES->{$type} || $type =~ m/^[A-Z]/);
340             return {
341 139         907 is_class => $is_class,
342             check => $ref_sub,
343             param_0 => $type_sub,
344             };
345             }
346             # boolean
347             sub check_parameter_definition {
348 54     54 1 67 my $class = $_[0];
349 54         62 my $param_type = $_[1];
350 54         53 my $check_def = $_[2];
351            
352 54 50       125 return 0 if(!$check_def->{'check'});
353            
354 54 50       119 return 1 if($CLASS_TYPES->{$param_type});
355 54 100       170 if($check_def->{'is_class'}) {
356 9         45 return UNIVERSAL::isa($param_type, $param_type);
357             }
358 45         280 1;
359             }
360             1;