File Coverage

blib/lib/Hash/AutoHash/Record.pm
Criterion Covered Total %
statement 204 204 100.0
branch 81 86 94.1
condition 16 18 88.8
subroutine 39 39 100.0
pod n/a
total 340 347 97.9


line stmt bran cond sub pod time code
1             package Hash::AutoHash::Record;
2             our $VERSION='1.17';
3             $VERSION=eval $VERSION; # I think this is the accepted idiom..
4              
5             #################################################################################
6             #
7             # Author: Nat Goodman
8             # Created: 09-03-05
9             # $Id:
10             #
11             # Flat and hierarchical record structures of the type encountered in Data::Pipeline
12             #
13             #################################################################################
14 13     13   204109 use strict;
  13         26  
  13         402  
15 13     13   65 use Carp;
  13         24  
  13         667  
16 13     13   11988 use Hash::AutoHash;
  13         129668  
  13         80  
17 13     13   3119 use base qw(Hash::AutoHash);
  13         27  
  13         2591  
18              
19             our @NORMAL_EXPORT_OK=@Hash::AutoHash::EXPORT_OK;
20             my $helper_class=__PACKAGE__.'::helper';
21             our @EXPORT_OK=$helper_class->EXPORT_OK;
22             our @SUBCLASS_EXPORT_OK=$helper_class->SUBCLASS_EXPORT_OK;
23              
24             #################################################################################
25             # helper package exists to avoid polluting Hash::AutoHash::Args namespace with
26             # subs that would mask accessor/mutator AUTOLOADs
27             # functions herein (except _new) are exportable by Hash::AutoHash::Args
28             #################################################################################
29             package Hash::AutoHash::Record::helper;
30             our $VERSION=$Hash::AutoHash::Record::VERSION;
31 13     13   67 use strict;
  13         22  
  13         427  
32 13     13   69 use Carp;
  13         21  
  13         888  
33             BEGIN {
34 13     13   438 our @ISA=qw(Hash::AutoHash::helper);
35             }
36 13     13   66 use Hash::AutoHash qw(autohash_tie);
  13         28  
  13         52  
37              
38             sub _new {
39 800     800   654280 my($helper_class,$class,@args)=@_;
40 800         3370 my $self=autohash_tie Hash::AutoHash::Record::tie,@args;
41 800         5749 bless $self,$class;
42             }
43             # Override autohash_clear to allow clearing of specific keys
44             sub autohash_clear {
45 5     5   1970 my $record=shift;
46 5         31 tied(%$record)->CLEAR(@_);
47             }
48              
49             #################################################################################
50             # Tied hash which implements Hash::AutoHash::Record
51             #################################################################################
52             package Hash::AutoHash::Record::tie;
53             our $VERSION=$Hash::AutoHash::Record::VERSION;
54 13     13   2586 use strict;
  13         23  
  13         431  
55 13     13   61 use Carp;
  13         18  
  13         777  
56 13     13   82 use Tie::Hash;
  13         20  
  13         289  
57 13     13   77 use Scalar::Util qw(reftype);
  13         35  
  13         658  
58 13     13   68 use List::MoreUtils qw(uniq);
  13         28  
  13         566  
59 13     13   14587 use Storable qw(dclone);
  13         56038  
  13         1154  
60 13     13   99 use Hash::AutoHash qw(autohash_alias);
  13         24  
  13         83  
61 13     13   13726 use Hash::AutoHash::AVPairsSingle;
  13         22928  
  13         88  
62 13     13   15699 use Hash::AutoHash::AVPairsMulti;
  13         68927  
  13         114  
63             our @ISA=qw(Tie::ExtraHash);
64              
65             my $i=0;
66 13     13   1210 use constant STORAGE=>$i++;
  13         25  
  13         712  
67 13     13   66 use constant DEFAULTS=>$i++;
  13         26  
  13         663  
68 13     13   67 use constant DEFAULT_TYPE_SCALAR=>$i++;
  13         22  
  13         550  
69 13     13   75 use constant DEFAULT_TYPE_ARRAY=>$i++;
  13         23  
  13         549  
70 13     13   69 use constant DEFAULT_TYPE_HASH=>$i++;
  13         30  
  13         575  
71 13     13   63 use constant UNIQUE=>$i++;
  13         24  
  13         641  
72 13     13   66 use constant FILTER=>$i++;
  13         25  
  13         27092  
73             # use constant FIELDS=>$i++;
74             # use constant TYPES=>$i++;
75              
76             # # undef means no type conversion
77             # our $default_type_scalar; # no type conversion
78             # our $default_type_array; # no type conversion
79             # our $default_type_hash='Hash::AutoHash';
80             # our $default_type_refhash='Hash::AutoHash::AVPairsMulti';
81              
82             sub TIEHASH {
83 800     800   8754 my($class,@hash)=@_;
84 800         2495 my $self=bless [],$class;
85             # use initial values (possibly flattened) as defaults
86 800         2728 my $defaults=$self->defaults(_flatten(@hash));
87 800 100       37794 $self->[STORAGE]=$defaults? dclone($defaults): {};
88 800         3143 $self;
89             }
90             sub FETCH {
91 24996     24996   17895942 my($self,$key)=@_;
92 24996         34670 my $storage=$self->[STORAGE];
93 24996         37642 my $value=$storage->{$key};
94 24996 100       50940 if (wantarray) {
95             # NG 09-10-12: line below was holdover from MultiValued. Not correct here
96             # return () unless defined $value;
97 4529 100       21323 return @$value if 'ARRAY' eq reftype($value);
98 2117 100       9582 return %$value if 'HASH' eq reftype($value);
99 571         2519 return ($value);
100             }
101 20467         74350 $value;
102             }
103             # FUTURE possibility: check whether hash locked & key exists
104              
105             sub STORE {
106 1691     1691   583957 my($self,$key,@values)=@_;
107 1691         2932 my $storage=$self->[STORAGE];
108 1691         4661 $self->_store($storage,$key,@values);
109 1687         5556 $self->FETCH($key);
110             }
111             sub CLEAR {
112 214     214   89955 my($self,@keys)=@_;
113 214         689 my $defaults=$self->defaults;
114 214 100       642 unless (@keys) {
115 211 100       13273 $self->[STORAGE]=$defaults? dclone($defaults): {}
116             } else { # clear specific keys
117 3         7 my $storage=$self->[STORAGE];
118 3         6 my $defaults=$self->[DEFAULTS];
119 3         9 for my $key (@keys) {
120 4         7 my $default=$defaults->{$key};
121 4         17 my $new=$self->_convert_initial_value($default);
122 4         20 $storage->{$key}=$default;
123             }}
124 214         1203 my $unique=$self->unique;
125 214 100       1008 $self->_unique($unique) if $unique;
126             }
127             *clear=\&CLEAR;
128              
129             # default values.
130             # can be set from initial values suppled to TIEHASH, or explicitly
131             sub defaults {
132 1120     1120   51836 my $self=shift;
133 1120         1582 my $defaults;
134 1120 100       2931 if (@_) { # set new value
135 553 100 100     4295 my @hash=(@_==1 && 'ARRAY' eq ref $_[0])? @{$_[0]}: (@_==1 && 'HASH' eq ref $_[0])? %{$_[0]}:
  2 100 66     5  
  8         56  
136             @_;
137 553         1088 $defaults={};
138 553         1561 while (@hash>1) { # store initial values
139 1622         3088 my($key,$value)=splice @hash,0,2; # shift 1st two elements
140 1622         4351 $self->_store($defaults,$key,$value);
141             }
142 553         1517 $self->[DEFAULTS]=$defaults; # set object attribute
143             } else { # get defaults from object
144 567         1164 $defaults=$self->[DEFAULTS];
145             }
146 1120 50       3475 wantarray? %{$defaults || {}}: $defaults;
  78 100       505  
147             }
148              
149             # forcibly assign value or set to undef
150             sub force {
151 250     250   12469 my($self,$key)=splice @_,0,2; # shift 1st 2 elements
152 250         321 my $storage=$self->[STORAGE];
153 250         396 $storage->{$key}=undef; # once field is undef, _store can do the rest
154 250 100       804 $self->_store($storage,$key,@_) if @_;
155 250         450 $self->FETCH($key);
156             }
157              
158             # code adapted from Hash::AutoHash::MultiValued
159             sub unique {
160 3098     3098   25090 my $self=shift;
161 3098 100       10918 return $self->[UNIQUE] unless @_;
162 586         1505 my $unique=$self->[UNIQUE]=shift;
163 586 100 100 117   3448 $unique=$self->[UNIQUE]=sub {$_[0] eq $_[1]} if $unique && 'CODE' ne ref $unique;
  117         749  
164 586 100       2026 $self->_unique($unique) if $unique;
165 586         1639 $unique;
166             }
167             sub _unique {
168 495     495   829 my($self,$unique)=@_;
169 495         740 my $storage=$self->[STORAGE];
170 495 50       1431 my @values=grep {defined $_ && 'ARRAY' eq reftype($_)} values %$storage;
  1464         7903  
171 495         1122 for my $values (@values) {
172 805 100       2948 next unless @$values;
173             # leave 1st value in @$values. put rest in @new_values
174 225         481 my @new_values=splice(@$values,1);
175 225         318 my($a,$b);
176 225         603 for $a (@new_values) {
177 79 100       180 push(@$values,$a) unless grep {$b=$_; &$unique($a,$b)} @$values;
  89         141  
  89         216  
178             }}
179             }
180             # code adapted from Hash::AutoHash::MultiValued
181             sub filter {
182 76     76   3255 my $self=shift;
183 76 100       323 my $filter=@_? $self->[FILTER]=shift: $self->[FILTER];
184 76 100       236 if ($filter) { # apply to existing values -- ARRAYs only
185 52 100       206 $filter=$self->[FILTER]=\&uniq unless 'CODE' eq ref $filter;
186 52         134 my $storage=$self->[STORAGE];
187 52 50       139 my @values=grep {defined $_ && 'ARRAY' eq reftype($_)} values %$storage;
  98         632  
188 52         108 map {@$_=&$filter(@$_)} @values; # updates each list in-place
  86         522  
189             }
190 76         356 $filter;
191             }
192              
193             # sub _default_type_scalar {shift->_default_type('scalar',@_);}
194             # sub _default_type_array {shift->_default_type('array',@_);}
195             # sub _default_type_hash {shift->_default_type('hash',@_);}
196             # sub _default_type_refhash {shift->_default_type('refhash',@_);}
197             # sub _default_type {
198             # my($self,$type)=splice @_,0,2;
199             # my $default;
200             # if (@_) { # set new value in object
201             # $default=$self->[uc "default_type_$type"]=$_[0];
202             # } else { # get defaults from object if possible, else from class
203             # $default=$self->[uc "default_type_$type"];
204             # unless (defined $default) { # now look in class
205             # my $class=ref $self;
206             # no strict 'refs';
207             # my $class_var=$class."::default_type_$type";
208             # $default=${$class_var};
209             # }}
210             # $default;
211             # }
212             # sub _convert_initial_value {
213             # my($self,$value)=@_;
214             # my $type=
215             # (!ref $value)? 'scalar':
216             # ('ARRAY' eq ref $value)? 'array':
217             # ('HASH' eq ref $value)? 'hash':
218             # ('REF' eq ref $value && 'HASH' eq ref $$value)? 'refhash':
219             # undef;
220             # my $class=$self->_default_type($type) if $type;
221             # $value=$class? new $class $value: $value;
222             # $value;
223             # }
224             sub _convert_initial_value {
225 1224     1224   1668 my($self,$value)=@_;
226 1224 100 100     5928 if ('HASH' eq ref $value) {
    100          
227             # attribute-single-value pair if no refs
228             # else attribute-multi-value pair if only refs are ARRAY
229             # else use as is
230 311         697 my @values=values %$value;
231             # CAUTION: doing grep below w/o map seems to stringify refs to things like ARRAY(0x1163510)
232 311         643 my @refs=grep {$_} map {ref $_} @values;
  68         166  
  68         244  
233 311 100       906 if (!@refs) {
    100          
234 263         1209 $value=new Hash::AutoHash::AVPairsSingle $value;
235             } elsif (!grep !/^ARRAY$/,@refs) {
236 21         116 $value=new Hash::AutoHash::AVPairsMulti $value;
237             }
238             } elsif ('REF' eq ref $value && 'HASH' eq ref $$value) {
239 247         624 my @values=values %$$value;
240             # CAUTION: doing grep below w/o map seems to stringify refs to things like ARRAY(0x1163510)
241 247         497 my @refs=grep {$_} map {ref $_} @values;
  4         14  
  4         14  
242 247 50       575 if (!grep !/^ARRAY$/,@refs) {
243 247         1122 $value=new Hash::AutoHash::AVPairsMulti $$value;
244             }}
245 1224         24873 $value;
246             }
247              
248             # logic: check type of old value and new value
249             # old undef. anything goes. new value replaces old w/ initial value conversion
250             # old scalar && new scalar. new value replaces old
251             # old ARRAY && new any value. multi-valued field. new pushed onto old & possibly uniqued
252             # old Hash::AutoHash. new must be HASH or ARRAY or list of key=>value pairs.
253             # new elements set in old using method notation
254             # old anything else. new value replaces old
255              
256             sub _store {
257 6056     6056   11969 my($self,$storage,$key,@new)=@_;
258 6056 100       21026 return unless @new;
259 4853         7742 my $old=$storage->{$key};
260 4853 100       14686 if (!defined $old) { # old undef. anything goes. new replaces old.
    100          
    100          
    100          
261 2495 100 66     12787 if (@new==1 && 'ARRAY' eq ref $new[0]) { # new multi-valued field
262 1292         3228 $storage->{$key}=[]; # initialize to empty ARRAY. recursion will do the rest
263             } else {
264 1203         1827 my $new1=shift @new;
265 1203         2877 $new1=$self->_convert_initial_value($new1);
266 1203         2637 $storage->{$key}=$new1;
267             }
268 2495         6210 $self->_store($storage,$key,@new); # recurse
269             } elsif (!ref $old) { # old scalar. new replaces old. must be scalar
270 17         37 my $new=shift @new;
271 17         61 $new=$self->_convert_initial_value($new);
272 17 100       330 confess "Trying to store multiple values in single-valued field $key" if @new;
273 16 50       42 confess "Trying to store reference in single-valued field $key" if ref $new;
274 16         47 $storage->{$key}=$new;
275             } elsif ('ARRAY' eq ref $old) { # old ARRAY. push new onto old. must be scalar
276             # $self->_store_multi($old,@new);
277             # code adapted from Hash::AutoHash::MultiValued
278 2296 100 100     14421 @new=@{$new[0]} if @new==1 && 'ARRAY' eq ref $new[0];
  2271         5606  
279 2296 100       13397 confess "Trying to store reference in multi-valued field $key" if grep {ref($_)} @new;
  1126         3808  
280 2295 100       6712 if (my $unique=$self->unique) {
281 586         694 my($a,$b);
282 586         1453 for $a (@new) {
283 251 100       798 push(@$old,$a) unless grep {$b=$_; &$unique($a,$b)} @$old;
  283         476  
  283         728  
284             }} else {
285 1709         6490 push(@$old,@new);
286             }
287             } elsif (UNIVERSAL::isa($old,'Hash::AutoHash')) { # old Hash::AutoHash
288 42         112 @new=_flatten(@new);
289 42         136 while (@new>1) { # store initial values
290 52         400 my($key,$value)=splice @new,0,2; # shift 1st two elements
291 52         454 $old->$key($value); # store using hash notation
292             }
293             } else { # old anything else.
294 3         13 $storage->{$key}=$new[0]; # new replaces old
295             }
296             }
297             # # store into multi-valued field, or store multi-value into undef field
298             # sub _store_multi {
299             # my($self,$old,@new)=@_;
300             # # code adapted from Hash::AutoHash::MultiValued
301             # @new=@{$new[0]} if @new==1 && 'ARRAY' eq ref $new[0];
302             # if (my $unique=$self->unique) {
303             # my($a,$b);
304             # for $a (@new) {
305             # push(@$old,$a) unless grep {$b=$_; &$unique($a,$b)} @$old;
306             # }} else {
307             # push(@$old,@new);
308             # }
309             # }
310             sub _flatten {
311 842 100   842   2884 if (@_==1) {
312 595 100       3240 return ('ARRAY' eq ref $_[0])? @{$_[0]}: ('HASH' eq ref $_[0])? %{$_[0]}: @_;
  5 100       22  
  587         3462  
313             }
314 247         965 @_;
315             }
316              
317             1;
318              
319             __END__