File Coverage

blib/lib/Paranoid/Data.pm
Criterion Covered Total %
statement 143 149 95.9
branch 55 66 83.3
condition 16 21 76.1
subroutine 18 18 100.0
pod 5 5 100.0
total 237 259 91.5


line stmt bran cond sub pod time code
1             # Paranoid::Data -- Misc. Data Manipulation Functions
2             #
3             # $Id: lib/Paranoid/Data.pm, 2.10 2022/03/08 00:01:04 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::Data;
33              
34 14     14   996 use 5.008;
  14         42  
35              
36 14     14   57 use strict;
  14         17  
  14         222  
37 14     14   57 use warnings;
  14         17  
  14         320  
38 14     14   57 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  14         29  
  14         814  
39 14     14   82 use base qw(Exporter);
  14         17  
  14         923  
40 14     14   82 use Paranoid;
  14         16  
  14         662  
41 14     14   499 use Paranoid::Debug qw(:all);
  14         15  
  14         1816  
42 14     14   73 use Carp;
  14         27  
  14         1580  
43              
44             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
45              
46             @EXPORT = qw(deepCopy deepCmp has64bInt quad2Longs longs2Quad);
47             @EXPORT_OK = @EXPORT;
48             %EXPORT_TAGS = ( all => [@EXPORT_OK], );
49              
50 14     14   85 use constant MAX32VAL => 0b11111111_11111111_11111111_11111111;
  14         52  
  14         794  
51 14     14   80 use constant TEST32INT => 1 << 32;
  14         17  
  14         15873  
52              
53             #####################################################################
54             #
55             # Module code follows
56             #
57             #####################################################################
58              
59             sub deepCopy (\[$@%]\[$@%]) {
60              
61             # Purpose: Attempts to safely copy an arbitrarily deep data
62             # structure from the source to the target
63             # Returns: True or False
64             # Usage: $rv = deepCopy($source, $target);
65             # Usage: $rv = deepCopy(@source, @target);
66             # Usage: $rv = deepCopy(%source, %target);
67              
68 9     9 1 6690 my $source = shift;
69 9         9 my $target = shift;
70 9         13 my $rv = 1;
71 9         23 my $counter = 0;
72 9 50       21 my $sref = defined $source ? ref $source : 'undef';
73 9 50       14 my $tref = defined $target ? ref $target : 'undef';
74 9         12 my ( @refs, $recurseSub );
75              
76 9         26 subPreamble( PDLEVEL1, '$$', $source, $target );
77              
78 9 50       19 croak 'source and target must be identical data types'
79             unless ref $sref eq ref $tref;
80              
81             $recurseSub = sub {
82 25     25   33 my $s = shift;
83 25         27 my $t = shift;
84 25         37 my $type = ref $s;
85 25         27 my $irv = 1;
86 25         27 my ( $key, $value );
87              
88             # We'll grep the @refs list to make sure there's no
89             # circular references going on
90 25 100       36 if ( grep { $_ eq $s } @refs ) {
  31         82  
91 1         3 Paranoid::ERROR = pdebug(
92             'Found a circular reference in data structure: ' . '(%s) %s',
93             PDLEVEL1, $s, @refs
94             );
95 1         5 return 0;
96             }
97              
98             # Push the reference onto the list
99 24         34 push @refs, $s;
100              
101             # Copy data over
102 24 100       46 if ( $type eq 'ARRAY' ) {
    50          
103              
104             # Copy over array elements
105 12         17 foreach my $element (@$s) {
106              
107 42         48 $type = ref $element;
108 42         39 $counter++;
109 42 100 100     85 if ( $type eq 'ARRAY' or $type eq 'HASH' ) {
110              
111             # Copy over sub arrays or hashes
112 7 100       12 push @$t, $type eq 'ARRAY' ? [] : {};
113 7 100       20 return 0 unless &$recurseSub( $element, $$t[-1] );
114              
115             } else {
116              
117             # Copy over everything else as-is
118 35         46 push @$t, $element;
119             }
120             }
121              
122             } elsif ( $type eq 'HASH' ) {
123 12         34 while ( ( $key, $value ) = each %$s ) {
124 34         48 $type = ref $value;
125 34         51 $counter++;
126 34 100 100     73 if ( $type eq 'ARRAY' or $type eq 'HASH' ) {
127              
128             # Copy over sub arrays or hashes
129 10 100       18 $$t{$key} = $type eq 'ARRAY' ? [] : {};
130 10 100       52 return 0 unless &$recurseSub( $value, $$t{$key} );
131              
132             } else {
133              
134             # Copy over everything else as-is
135 24         56 $$t{$key} = $value;
136             }
137             }
138             }
139              
140             # We're done, so let's remove the reference we were working on
141 20         21 pop @refs;
142              
143 20         45 return 1;
144 9         36 };
145              
146             # Start the copy
147 9 100 100     29 if ( $sref eq 'ARRAY' or $sref eq 'HASH' ) {
148              
149             # Copy over arrays & hashes
150 8 100       11 if ( $sref eq 'ARRAY' ) {
151 4         8 @$target = ();
152             } else {
153 4         16 %$target = ();
154             }
155 8         13 $rv = &$recurseSub( $source, $target );
156              
157             } else {
158              
159             # Copy over everything else directly
160 1         2 $$target = $$source;
161 1         2 $counter++;
162             }
163              
164 9 100       16 $rv = $counter if $rv;
165              
166 9         23 subPostamble( PDLEVEL1, '$', $rv );
167              
168 9         18 return $rv;
169             }
170              
171             sub _cmpArray (\@\@) {
172              
173             # Purpose: Compares arrays, returns true if identical
174             # Returns: Boolean
175             # Usage: $rv = _cmpArray(@array1, @array2);
176              
177 41     41   49 my $ref1 = shift;
178 41         41 my $ref2 = shift;
179 41         45 my $rv = 1;
180 41         39 my $i = 0;
181 41         46 my ( $n, $d1, $d2, $t1, $t2 );
182              
183 41         83 subPreamble( PDLEVEL2, '$$', $ref1, $ref2 );
184              
185 41         55 $rv = scalar @$ref1 == scalar @$ref2;
186 41         44 $n = scalar @$ref1;
187              
188             # Compare contents if there is any
189 41 100 100     134 if ( $rv and $n ) {
190 39         61 while ( $i <= $n ) {
191              
192             # Collect some meta data
193 172         193 $d1 = defined $$ref1[$i];
194 172         197 $d2 = defined $$ref2[$i];
195 172 100       226 $t1 = $d1 ? ref $$ref1[$i] : 'undef';
196 172 100       205 $t2 = $d2 ? ref $$ref2[$i] : 'undef';
197              
198 172 100       196 if ( $d1 == $d2 ) {
199              
200             # Both are undefined, so move to the next item
201 170 100       223 unless ($d1) {
202 41         40 $i++;
203 41         68 next;
204             }
205              
206             # Both are defined, so check for type
207 129         125 $rv = $t1 eq $t2;
208              
209 129 100       167 if ($rv) {
210              
211             # The types are the same, so do some comparisons
212 127 100       174 if ( $t1 eq 'ARRAY' ) {
    100          
213 6         12 $rv = deepCmp( $$ref1[$i], $$ref2[$i] );
214             } elsif ( $t1 eq 'HASH' ) {
215 9         17 $rv = deepCmp( $$ref1[$i], $$ref2[$i] );
216             } else {
217              
218             # Compare scalar value of all other types
219 112         149 $rv = $$ref1[$i] eq $$ref2[$i];
220             }
221             }
222              
223             } else {
224              
225             # One of the two are undefined, so quick exit
226 2         3 $rv = 0;
227             }
228              
229             # Early exit if we've found a difference already
230 131 100       165 last unless $rv;
231              
232             # Otherwise, on to the next element
233 120         146 $i++;
234             }
235             }
236              
237             # A little explicit sanitizing of input for false returns
238 41 100       51 $rv = 0 unless $rv;
239              
240 41         81 subPostamble( PDLEVEL2, '$', $rv );
241              
242 41         63 return $rv;
243             }
244              
245             sub _cmpHash (\%\%) {
246              
247             # Purpose: Compares hashes, returns true if identical
248             # Returns: Boolean
249             # Usage: $rv = _cmpHash(%hash1, %hash2);
250              
251 13     13   15 my $ref1 = shift;
252 13         14 my $ref2 = shift;
253 13         13 my $rv = 1;
254 13         15 my ( @k1, @k2, @v1, @v2 );
255              
256 13         27 subPreamble( PDLEVEL2, '$$', $ref1, $ref2 );
257              
258 13         45 @k1 = sort keys %$ref1;
259 13         37 @k2 = sort keys %$ref2;
260              
261             # Compare first by key list
262 13         25 $rv = _cmpArray( @k1, @k2 );
263              
264 13 100       22 if ($rv) {
265              
266             # Compare by value list
267 12         19 foreach (@k1) {
268 40         49 push @v1, $$ref1{$_};
269 40         47 push @v2, $$ref2{$_};
270             }
271 12         28 $rv = _cmpArray( @v1, @v2 );
272             }
273              
274 13         27 subPostamble( PDLEVEL2, '$', $rv );
275              
276 13         28 return $rv;
277             }
278              
279             sub deepCmp (\[$@%]\[$@%]) {
280              
281             # Purpose: Compares data structures, returns true if identical
282             # Returns: Boolean
283             # Usage: $rv = deepCmp(%hash1, %hash2);
284             # Usage: $rv = deepCmp(@array1, @arrays2);
285              
286 29     29 1 490 my $ref1 = shift;
287 29         34 my $ref2 = shift;
288 29         37 my $rv = 1;
289              
290 29         65 subPreamble( PDLEVEL1, '$$', $ref1, $ref2 );
291              
292 29 50       59 unless ( ref $ref1 eq ref $ref1 ) {
293 0         0 $rv = 0;
294 0         0 Paranoid::ERROR =
295             pdebug( 'data structures are not the same type', PDLEVEL1 );
296             }
297              
298 29 50 33     136 if ( $rv and ref $ref1 eq 'SCALAR' ) {
    100 66        
    50 33        
299 0         0 $rv = $ref1 eq $ref2;
300             } elsif ( $rv and ref $ref1 eq 'ARRAY' ) {
301 16         30 $rv = _cmpArray( @$ref1, @$ref2 );
302             } elsif ( $rv and ref $ref1 eq 'HASH' ) {
303 13         30 $rv = _cmpHash( %$ref1, %$ref2 );
304             } else {
305 0         0 $rv = 0;
306 0         0 Paranoid::ERROR =
307             pdebug( 'called with non-simple data types', PDLEVEL1 );
308             }
309              
310 29         63 subPostamble( PDLEVEL1, '$', $rv );
311              
312 29         89 return $rv;
313             }
314              
315             sub has64bInt {
316              
317             # Purpose: Returns whether the current platform supports 64b integers
318             # Returns: Boolean
319             # Usage: $rv = has64bInt();
320              
321 12989     12989 1 26039 return TEST32INT == 1 ? 0 : 1;
322             }
323              
324             sub quad2Longs {
325              
326             # Purpose: Splits a quad into long integers
327             # Returns: Array of Longs (low bytes, high bytes)
328             # Usage: ($low, $high) = quad2Longs($quad);
329              
330 1321     1321 1 2763 my $quad = shift;
331 1321         2429 my ( $upper, $lower );
332              
333             # Extract lower 32 bits
334 1321         2927 $lower = $quad & MAX32VAL;
335              
336             # Extract upper 32 bits
337 1321 50       3099 $upper = has64bInt() ? ( $quad & ~MAX32VAL ) >> 32 : 0;
338              
339 1321         7292 return ( $lower, $upper );
340             }
341              
342             sub longs2Quad {
343              
344             # Purpose: Joins two longs into a quad (if supported)
345             # Returns: Quad Integer/undef
346             # Usage: $quad = longs2Quad($low, $high);
347              
348 11668     11668 1 24113 my $low = shift;
349 11668         18503 my $high = shift;
350 11668         14994 my $quad;
351              
352 11668 50       21910 if ( has64bInt() ) {
353 11668         32573 $quad = $low | ( $high << 32 );
354             } else {
355 0 0       0 $quad = $low if $high == 0;
356             }
357              
358 11668         35556 return $quad;
359             }
360              
361             1;
362              
363             __END__