File Coverage

blib/lib/Paranoid/Data.pm
Criterion Covered Total %
statement 133 138 96.3
branch 53 60 88.3
condition 16 21 76.1
subroutine 13 13 100.0
pod 2 2 100.0
total 217 234 92.7


line stmt bran cond sub pod time code
1             # Paranoid::Data -- Misc. Data Manipulation Functions
2             #
3             # $Id: lib/Paranoid/Data.pm, 2.09 2021/12/28 15:46:49 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 2     2   1059 use 5.008;
  2         7  
35              
36 2     2   11 use strict;
  2         4  
  2         40  
37 2     2   9 use warnings;
  2         3  
  2         58  
38 2     2   8 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         5  
  2         127  
39 2     2   12 use base qw(Exporter);
  2         4  
  2         140  
40 2     2   11 use Paranoid;
  2         5  
  2         99  
41 2     2   12 use Paranoid::Debug qw(:all);
  2         3  
  2         351  
42 2     2   16 use Carp;
  2         3  
  2         2587  
43              
44             ($VERSION) = ( q$Revision: 2.09 $ =~ /(\d+(?:\.\d+)+)/sm );
45              
46             @EXPORT = qw(deepCopy deepCmp);
47             @EXPORT_OK = @EXPORT;
48             %EXPORT_TAGS = ( all => [@EXPORT_OK], );
49              
50             #####################################################################
51             #
52             # Module code follows
53             #
54             #####################################################################
55              
56             sub deepCopy (\[$@%]\[$@%]) {
57              
58             # Purpose: Attempts to safely copy an arbitrarily deep data
59             # structure from the source to the target
60             # Returns: True or False
61             # Usage: $rv = deepCopy($source, $target);
62             # Usage: $rv = deepCopy(@source, @target);
63             # Usage: $rv = deepCopy(%source, %target);
64              
65 9     9 1 10140 my $source = shift;
66 9         14 my $target = shift;
67 9         13 my $rv = 1;
68 9         13 my $counter = 0;
69 9 50       25 my $sref = defined $source ? ref $source : 'undef';
70 9 50       18 my $tref = defined $target ? ref $target : 'undef';
71 9         17 my ( @refs, $recurseSub );
72              
73 9 50       24 croak 'source and target must be identical data types'
74             unless ref $sref eq ref $tref;
75              
76 9         28 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $source, $target );
77 9         26 pIn();
78              
79             $recurseSub = sub {
80 25     25   35 my $s = shift;
81 25         30 my $t = shift;
82 25         42 my $type = ref $s;
83 25         38 my $irv = 1;
84 25         49 my ( $key, $value );
85              
86             # We'll grep the @refs list to make sure there's no
87             # circular references going on
88 25 100       46 if ( grep { $_ eq $s } @refs ) {
  31         85  
89 1         4 Paranoid::ERROR = pdebug(
90             'Found a circular reference in data structure: ' . '(%s) %s',
91             PDLEVEL1, $s, @refs
92             );
93 1         7 return 0;
94             }
95              
96             # Push the reference onto the list
97 24         36 push @refs, $s;
98              
99             # Copy data over
100 24 100       52 if ( $type eq 'ARRAY' ) {
    50          
101              
102             # Copy over array elements
103 12         19 foreach my $element (@$s) {
104              
105 42         57 $type = ref $element;
106 42         47 $counter++;
107 42 100 100     107 if ( $type eq 'ARRAY' or $type eq 'HASH' ) {
108              
109             # Copy over sub arrays or hashes
110 7 100       16 push @$t, $type eq 'ARRAY' ? [] : {};
111 7 100       26 return 0 unless &$recurseSub( $element, $$t[-1] );
112              
113             } else {
114              
115             # Copy over everything else as-is
116 35         55 push @$t, $element;
117             }
118             }
119              
120             } elsif ( $type eq 'HASH' ) {
121 12         41 while ( ( $key, $value ) = each %$s ) {
122 34         54 $type = ref $value;
123 34         39 $counter++;
124 34 100 100     91 if ( $type eq 'ARRAY' or $type eq 'HASH' ) {
125              
126             # Copy over sub arrays or hashes
127 10 100       36 $$t{$key} = $type eq 'ARRAY' ? [] : {};
128 10 100       35 return 0 unless &$recurseSub( $value, $$t{$key} );
129              
130             } else {
131              
132             # Copy over everything else as-is
133 24         69 $$t{$key} = $value;
134             }
135             }
136             }
137              
138             # We're done, so let's remove the reference we were working on
139 20         24 pop @refs;
140              
141 20         59 return 1;
142 9         45 };
143              
144             # Start the copy
145 9 100 100     37 if ( $sref eq 'ARRAY' or $sref eq 'HASH' ) {
146              
147             # Copy over arrays & hashes
148 8 100       19 if ( $sref eq 'ARRAY' ) {
149 4         10 @$target = ();
150             } else {
151 4         9 %$target = ();
152             }
153 8         39 $rv = &$recurseSub( $source, $target );
154              
155             } else {
156              
157             # Copy over everything else directly
158 1         14 $$target = $$source;
159 1         3 $counter++;
160             }
161              
162 9 100       18 $rv = $counter if $rv;
163              
164 9         24 pOut();
165 9         21 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
166              
167 9         23 return $rv;
168             }
169              
170             sub _cmpArray (\@\@) {
171              
172             # Purpose: Compares arrays, returns true if identical
173             # Returns: Boolean
174             # Usage: $rv = _cmpArray(@array1, @array2);
175              
176 41     41   59 my $ref1 = shift;
177 41         50 my $ref2 = shift;
178 41         53 my $rv = 1;
179 41         46 my $i = 0;
180 41         61 my ( $n, $d1, $d2, $t1, $t2 );
181              
182 41         93 pdebug( 'entering w/%s %s', PDLEVEL2, $ref1, $ref2 );
183 41         95 pIn();
184              
185 41         59 $rv = scalar @$ref1 == scalar @$ref2;
186 41         60 $n = scalar @$ref1;
187              
188             # Compare contents if there is any
189 41 100 100     129 if ( $rv and $n ) {
190 39         65 while ( $i <= $n ) {
191              
192             # Collect some meta data
193 172         225 $d1 = defined $$ref1[$i];
194 172         214 $d2 = defined $$ref2[$i];
195 172 100       297 $t1 = $d1 ? ref $$ref1[$i] : 'undef';
196 172 100       252 $t2 = $d2 ? ref $$ref2[$i] : 'undef';
197              
198 172 100       277 if ( $d1 == $d2 ) {
199              
200             # Both are undefined, so move to the next item
201 170 100       272 unless ($d1) {
202 41         46 $i++;
203 41         83 next;
204             }
205              
206             # Both are defined, so check for type
207 129         168 $rv = $t1 eq $t2;
208              
209 129 100       193 if ($rv) {
210              
211             # The types are the same, so do some comparisons
212 127 100       214 if ( $t1 eq 'ARRAY' ) {
    100          
213 6         11 $rv = deepCmp( $$ref1[$i], $$ref2[$i] );
214             } elsif ( $t1 eq 'HASH' ) {
215 9         22 $rv = deepCmp( $$ref1[$i], $$ref2[$i] );
216             } else {
217              
218             # Compare scalar value of all other types
219 112         157 $rv = $$ref1[$i] eq $$ref2[$i];
220             }
221             }
222              
223             } else {
224              
225             # One of the two are undefined, so quick exit
226 2         4 $rv = 0;
227             }
228              
229             # Early exit if we've found a difference already
230 131 100       211 last unless $rv;
231              
232             # Otherwise, on to the next element
233 120         187 $i++;
234             }
235             }
236              
237             # A little explicit sanitizing of input for false returns
238 41 100       65 $rv = 0 unless $rv;
239              
240 41         96 pOut();
241 41         83 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
242              
243 41         86 return $rv;
244             }
245              
246             sub _cmpHash (\%\%) {
247              
248             # Purpose: Compares hashes, returns true if identical
249             # Returns: Boolean
250             # Usage: $rv = _cmpHash(%hash1, %hash2);
251              
252 13     13   17 my $ref1 = shift;
253 13         17 my $ref2 = shift;
254 13         17 my $rv = 1;
255 13         18 my ( @k1, @k2, @v1, @v2 );
256              
257 13         31 pdebug( 'entering w/%s %s', PDLEVEL2, $ref1, $ref2 );
258 13         31 pIn();
259              
260 13         57 @k1 = sort keys %$ref1;
261 13         40 @k2 = sort keys %$ref2;
262              
263             # Compare first by key list
264 13         33 $rv = _cmpArray( @k1, @k2 );
265              
266 13 100       38 if ($rv) {
267              
268             # Compare by value list
269 12         27 foreach (@k1) {
270 40         54 push @v1, $$ref1{$_};
271 40         62 push @v2, $$ref2{$_};
272             }
273 12         22 $rv = _cmpArray( @v1, @v2 );
274             }
275              
276 13         32 pOut();
277 13         26 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
278              
279 13         34 return $rv;
280             }
281              
282             sub deepCmp (\[$@%]\[$@%]) {
283              
284             # Purpose: Compares data structures, returns true if identical
285             # Returns: Boolean
286             # Usage: $rv = deepCmp(%hash1, %hash2);
287             # Usage: $rv = deepCmp(@array1, @arrays2);
288              
289 29     29 1 707 my $ref1 = shift;
290 29         33 my $ref2 = shift;
291 29         47 my $rv = 1;
292              
293 29         70 pdebug( 'entering w/%s %s', PDLEVEL1, $ref1, $ref2 );
294 29         74 pIn();
295              
296 29 50       69 unless ( ref $ref1 eq ref $ref1 ) {
297 0         0 $rv = 0;
298 0         0 Paranoid::ERROR =
299             pdebug( 'data structures are not the same type', PDLEVEL1 );
300             }
301              
302 29 50 33     173 if ( $rv and ref $ref1 eq 'SCALAR' ) {
    100 66        
    50 33        
303 0         0 $rv = $ref1 eq $ref2;
304             } elsif ( $rv and ref $ref1 eq 'ARRAY' ) {
305 16         37 $rv = _cmpArray( @$ref1, @$ref2 );
306             } elsif ( $rv and ref $ref1 eq 'HASH' ) {
307 13         36 $rv = _cmpHash( %$ref1, %$ref2 );
308             } else {
309 0         0 $rv = 0;
310 0         0 Paranoid::ERROR =
311             pdebug( 'called with non-simple data types', PDLEVEL1 );
312             }
313              
314 29         67 pOut();
315 29         59 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
316              
317 29         100 return $rv;
318             }
319              
320             1;
321              
322             __END__