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.08 2020/12/31 12:10:06 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   1045 use 5.008;
  2         9  
35              
36 2     2   10 use strict;
  2         5  
  2         42  
37 2     2   9 use warnings;
  2         4  
  2         54  
38 2     2   9 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         4  
  2         138  
39 2     2   14 use base qw(Exporter);
  2         4  
  2         123  
40 2     2   12 use Paranoid;
  2         4  
  2         108  
41 2     2   14 use Paranoid::Debug qw(:all);
  2         4  
  2         361  
42 2     2   16 use Carp;
  2         3  
  2         2637  
43              
44             ($VERSION) = ( q$Revision: 2.08 $ =~ /(\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 8535 my $source = shift;
66 9         16 my $target = shift;
67 9         28 my $rv = 1;
68 9         14 my $counter = 0;
69 9 50       27 my $sref = defined $source ? ref $source : 'undef';
70 9 50       19 my $tref = defined $target ? ref $target : 'undef';
71 9         14 my ( @refs, $recurseSub );
72              
73 9 50       27 croak 'source and target must be identical data types'
74             unless ref $sref eq ref $tref;
75              
76 9         31 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $source, $target );
77 9         27 pIn();
78              
79             $recurseSub = sub {
80 25     25   40 my $s = shift;
81 25         35 my $t = shift;
82 25         48 my $type = ref $s;
83 25         28 my $irv = 1;
84 25         38 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       48 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         78 push @refs, $s;
98              
99             # Copy data over
100 24 100       51 if ( $type eq 'ARRAY' ) {
    50          
101              
102             # Copy over array elements
103 12         21 foreach my $element (@$s) {
104              
105 42         68 $type = ref $element;
106 42         50 $counter++;
107 42 100 100     111 if ( $type eq 'ARRAY' or $type eq 'HASH' ) {
108              
109             # Copy over sub arrays or hashes
110 7 100       17 push @$t, $type eq 'ARRAY' ? [] : {};
111 7 100       25 return 0 unless &$recurseSub( $element, $$t[-1] );
112              
113             } else {
114              
115             # Copy over everything else as-is
116 35         64 push @$t, $element;
117             }
118             }
119              
120             } elsif ( $type eq 'HASH' ) {
121 12         39 while ( ( $key, $value ) = each %$s ) {
122 34         63 $type = ref $value;
123 34         43 $counter++;
124 34 100 100     97 if ( $type eq 'ARRAY' or $type eq 'HASH' ) {
125              
126             # Copy over sub arrays or hashes
127 10 100       33 $$t{$key} = $type eq 'ARRAY' ? [] : {};
128 10 100       36 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         29 pop @refs;
140              
141 20         54 return 1;
142 9         48 };
143              
144             # Start the copy
145 9 100 100     47 if ( $sref eq 'ARRAY' or $sref eq 'HASH' ) {
146              
147             # Copy over arrays & hashes
148 8 100       18 if ( $sref eq 'ARRAY' ) {
149 4         10 @$target = ();
150             } else {
151 4         11 %$target = ();
152             }
153 8         23 $rv = &$recurseSub( $source, $target );
154              
155             } else {
156              
157             # Copy over everything else directly
158 1         5 $$target = $$source;
159 1         2 $counter++;
160             }
161              
162 9 100       18 $rv = $counter if $rv;
163              
164 9         30 pOut();
165 9         21 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
166              
167 9         26 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   62 my $ref1 = shift;
177 41         54 my $ref2 = shift;
178 41         57 my $rv = 1;
179 41         57 my $i = 0;
180 41         58 my ( $n, $d1, $d2, $t1, $t2 );
181              
182 41         106 pdebug( 'entering w/%s %s', PDLEVEL2, $ref1, $ref2 );
183 41         100 pIn();
184              
185 41         70 $rv = scalar @$ref1 == scalar @$ref2;
186 41         56 $n = scalar @$ref1;
187              
188             # Compare contents if there is any
189 41 100 100     127 if ( $rv and $n ) {
190 39         74 while ( $i <= $n ) {
191              
192             # Collect some meta data
193 172         250 $d1 = defined $$ref1[$i];
194 172         240 $d2 = defined $$ref2[$i];
195 172 100       284 $t1 = $d1 ? ref $$ref1[$i] : 'undef';
196 172 100       267 $t2 = $d2 ? ref $$ref2[$i] : 'undef';
197              
198 172 100       249 if ( $d1 == $d2 ) {
199              
200             # Both are undefined, so move to the next item
201 170 100       280 unless ($d1) {
202 41         57 $i++;
203 41         78 next;
204             }
205              
206             # Both are defined, so check for type
207 129         180 $rv = $t1 eq $t2;
208              
209 129 100       212 if ($rv) {
210              
211             # The types are the same, so do some comparisons
212 127 100       216 if ( $t1 eq 'ARRAY' ) {
    100          
213 6         15 $rv = deepCmp( $$ref1[$i], $$ref2[$i] );
214             } elsif ( $t1 eq 'HASH' ) {
215 9         21 $rv = deepCmp( $$ref1[$i], $$ref2[$i] );
216             } else {
217              
218             # Compare scalar value of all other types
219 112         179 $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       225 last unless $rv;
231              
232             # Otherwise, on to the next element
233 120         192 $i++;
234             }
235             }
236              
237             # A little explicit sanitizing of input for false returns
238 41 100       95 $rv = 0 unless $rv;
239              
240 41         105 pOut();
241 41         96 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
242              
243 41         91 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   21 my $ref1 = shift;
253 13         18 my $ref2 = shift;
254 13         16 my $rv = 1;
255 13         22 my ( @k1, @k2, @v1, @v2 );
256              
257 13         32 pdebug( 'entering w/%s %s', PDLEVEL2, $ref1, $ref2 );
258 13         34 pIn();
259              
260 13         59 @k1 = sort keys %$ref1;
261 13         55 @k2 = sort keys %$ref2;
262              
263             # Compare first by key list
264 13         33 $rv = _cmpArray( @k1, @k2 );
265              
266 13 100       27 if ($rv) {
267              
268             # Compare by value list
269 12         23 foreach (@k1) {
270 40         63 push @v1, $$ref1{$_};
271 40         61 push @v2, $$ref2{$_};
272             }
273 12         23 $rv = _cmpArray( @v1, @v2 );
274             }
275              
276 13         43 pOut();
277 13         30 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
278              
279 13         39 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 637 my $ref1 = shift;
290 29         42 my $ref2 = shift;
291 29         41 my $rv = 1;
292              
293 29         82 pdebug( 'entering w/%s %s', PDLEVEL1, $ref1, $ref2 );
294 29         77 pIn();
295              
296 29 50       73 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     182 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         41 $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         75 pOut();
315 29         63 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
316              
317 29         106 return $rv;
318             }
319              
320             1;
321              
322             __END__