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_01';
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   129648 use strict;
  13         17  
  13         304  
15 13     13   46 use Carp;
  13         14  
  13         543  
16 13     13   5851 use Hash::AutoHash;
  13         150439  
  13         65  
17 13     13   2278 use base qw(Hash::AutoHash);
  13         20  
  13         2039  
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   60 use strict;
  13         22  
  13         240  
32 13     13   43 use Carp;
  13         16  
  13         693  
33             BEGIN {
34 13     13   285 our @ISA=qw(Hash::AutoHash::helper);
35             }
36 13     13   50 use Hash::AutoHash qw(autohash_tie);
  13         13  
  13         37  
37              
38             sub _new {
39 800     800   386079 my($helper_class,$class,@args)=@_;
40 800         2717 my $self=autohash_tie Hash::AutoHash::Record::tie,@args;
41 800         3441 bless $self,$class;
42             }
43             # Override autohash_clear to allow clearing of specific keys
44             sub autohash_clear {
45 5     5   845 my $record=shift;
46 5         23 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   1918 use strict;
  13         16  
  13         229  
55 13     13   43 use Carp;
  13         12  
  13         649  
56 13     13   52 use Tie::Hash;
  13         13  
  13         267  
57 13     13   41 use Scalar::Util qw(reftype);
  13         14  
  13         510  
58 13     13   46 use List::MoreUtils qw(uniq);
  13         13  
  13         79  
59 13     13   11629 use Storable qw(dclone);
  13         32834  
  13         808  
60 13     13   72 use Hash::AutoHash qw(autohash_alias);
  13         16  
  13         85  
61 13     13   7260 use Hash::AutoHash::AVPairsSingle;
  13         16952  
  13         840  
62 13     13   7050 use Hash::AutoHash::AVPairsMulti;
  13         46940  
  13         81  
63             our @ISA=qw(Tie::ExtraHash);
64              
65             my $i=0;
66 13     13   1040 use constant STORAGE=>$i++;
  13         21  
  13         623  
67 13     13   51 use constant DEFAULTS=>$i++;
  13         15  
  13         535  
68 13     13   52 use constant DEFAULT_TYPE_SCALAR=>$i++;
  13         17  
  13         549  
69 13     13   47 use constant DEFAULT_TYPE_ARRAY=>$i++;
  13         14  
  13         524  
70 13     13   47 use constant DEFAULT_TYPE_HASH=>$i++;
  13         17  
  13         522  
71 13     13   52 use constant UNIQUE=>$i++;
  13         13  
  13         523  
72 13     13   48 use constant FILTER=>$i++;
  13         15  
  13         17355  
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   6695 my($class,@hash)=@_;
84 800         1514 my $self=bless [],$class;
85             # use initial values (possibly flattened) as defaults
86 800         2280 my $defaults=$self->defaults(_flatten(@hash));
87 800 100       26453 $self->[STORAGE]=$defaults? dclone($defaults): {};
88 800         2421 $self;
89             }
90             sub FETCH {
91 24996     24996   10090436 my($self,$key)=@_;
92 24996         23161 my $storage=$self->[STORAGE];
93 24996         22404 my $value=$storage->{$key};
94 24996 100       35983 if (wantarray) {
95             # NG 09-10-12: line below was holdover from MultiValued. Not correct here
96             # return () unless defined $value;
97 4529 100       16022 return @$value if 'ARRAY' eq reftype($value);
98 2117 100       7289 return %$value if 'HASH' eq reftype($value);
99 571         1622 return ($value);
100             }
101 20467         34196 $value;
102             }
103             # FUTURE possibility: check whether hash locked & key exists
104              
105             sub STORE {
106 1691     1691   330589 my($self,$key,@values)=@_;
107 1691         2106 my $storage=$self->[STORAGE];
108 1691         3638 $self->_store($storage,$key,@values);
109 1687         3598 $self->FETCH($key);
110             }
111             sub CLEAR {
112 214     214   51846 my($self,@keys)=@_;
113 214         640 my $defaults=$self->defaults;
114 214 100       485 unless (@keys) {
115 211 100       8976 $self->[STORAGE]=$defaults? dclone($defaults): {}
116             } else { # clear specific keys
117 3         7 my $storage=$self->[STORAGE];
118 3         5 my $defaults=$self->[DEFAULTS];
119 3         9 for my $key (@keys) {
120 4         5 my $default=$defaults->{$key};
121 4         15 my $new=$self->_convert_initial_value($default);
122 4         15 $storage->{$key}=$default;
123             }}
124 214         830 my $unique=$self->unique;
125 214 100       834 $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   31956 my $self=shift;
133 1120         1218 my $defaults;
134 1120 100       2336 if (@_) { # set new value
135 553 100 100     3621 my @hash=(@_==1 && 'ARRAY' eq ref $_[0])? @{$_[0]}: (@_==1 && 'HASH' eq ref $_[0])? %{$_[0]}:
  2 100 66     3  
  8         27  
136             @_;
137 553         820 $defaults={};
138 553         1476 while (@hash>1) { # store initial values
139 1622         2064 my($key,$value)=splice @hash,0,2; # shift 1st two elements
140 1622         2530 $self->_store($defaults,$key,$value);
141             }
142 553         930 $self->[DEFAULTS]=$defaults; # set object attribute
143             } else { # get defaults from object
144 567         954 $defaults=$self->[DEFAULTS];
145             }
146 1120 50       2090 wantarray? %{$defaults || {}}: $defaults;
  78 100       383  
147             }
148              
149             # forcibly assign value or set to undef
150             sub force {
151 250     250   7082 my($self,$key)=splice @_,0,2; # shift 1st 2 elements
152 250         193 my $storage=$self->[STORAGE];
153 250         273 $storage->{$key}=undef; # once field is undef, _store can do the rest
154 250 100       499 $self->_store($storage,$key,@_) if @_;
155 250         263 $self->FETCH($key);
156             }
157              
158             # code adapted from Hash::AutoHash::MultiValued
159             sub unique {
160 3098     3098   17699 my $self=shift;
161 3098 100       7945 return $self->[UNIQUE] unless @_;
162 586         1218 my $unique=$self->[UNIQUE]=shift;
163 586 100 100 117   3050 $unique=$self->[UNIQUE]=sub {$_[0] eq $_[1]} if $unique && 'CODE' ne ref $unique;
  117         516  
164 586 100       1871 $self->_unique($unique) if $unique;
165 586         945 $unique;
166             }
167             sub _unique {
168 495     495   647 my($self,$unique)=@_;
169 495         677 my $storage=$self->[STORAGE];
170 495 50       966 my @values=grep {defined $_ && 'ARRAY' eq reftype($_)} values %$storage;
  1464         5413  
171 495         934 for my $values (@values) {
172 805 100       2043 next unless @$values;
173             # leave 1st value in @$values. put rest in @new_values
174 225         390 my @new_values=splice(@$values,1);
175 225         266 my($a,$b);
176 225         447 for $a (@new_values) {
177 79 100       171 push(@$values,$a) unless grep {$b=$_; &$unique($a,$b)} @$values;
  89         108  
  89         176  
178             }}
179             }
180             # code adapted from Hash::AutoHash::MultiValued
181             sub filter {
182 76     76   2119 my $self=shift;
183 76 100       253 my $filter=@_? $self->[FILTER]=shift: $self->[FILTER];
184 76 100       206 if ($filter) { # apply to existing values -- ARRAYs only
185 52 100       206 $filter=$self->[FILTER]=\&uniq unless 'CODE' eq ref $filter;
186 52         70 my $storage=$self->[STORAGE];
187 52 50       177 my @values=grep {defined $_ && 'ARRAY' eq reftype($_)} values %$storage;
  98         501  
188 52         77 map {@$_=&$filter(@$_)} @values; # updates each list in-place
  86         522  
189             }
190 76         344 $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   1116 my($self,$value)=@_;
226 1224 100 100     4122 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         498 my @values=values %$value;
231             # CAUTION: doing grep below w/o map seems to stringify refs to things like ARRAY(0x1163510)
232 311         465 my @refs=grep {$_} map {ref $_} @values;
  68         105  
  68         114  
233 311 100       587 if (!@refs) {
    100          
234 263         721 $value=new Hash::AutoHash::AVPairsSingle $value;
235             } elsif (!grep !/^ARRAY$/,@refs) {
236 21         85 $value=new Hash::AutoHash::AVPairsMulti $value;
237             }
238             } elsif ('REF' eq ref $value && 'HASH' eq ref $$value) {
239 247         625 my @values=values %$$value;
240             # CAUTION: doing grep below w/o map seems to stringify refs to things like ARRAY(0x1163510)
241 247         429 my @refs=grep {$_} map {ref $_} @values;
  4         10  
  4         10  
242 247 50       619 if (!grep !/^ARRAY$/,@refs) {
243 247         796 $value=new Hash::AutoHash::AVPairsMulti $$value;
244             }}
245 1224         15768 $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   7575 my($self,$storage,$key,@new)=@_;
258 6056 100       10386 return unless @new;
259 4853         4612 my $old=$storage->{$key};
260 4853 100       9980 if (!defined $old) { # old undef. anything goes. new replaces old.
    100          
    100          
    100          
261 2495 100 66     8805 if (@new==1 && 'ARRAY' eq ref $new[0]) { # new multi-valued field
262 1292         1988 $storage->{$key}=[]; # initialize to empty ARRAY. recursion will do the rest
263             } else {
264 1203         1146 my $new1=shift @new;
265 1203         1823 $new1=$self->_convert_initial_value($new1);
266 1203         1634 $storage->{$key}=$new1;
267             }
268 2495         3746 $self->_store($storage,$key,@new); # recurse
269             } elsif (!ref $old) { # old scalar. new replaces old. must be scalar
270 17         25 my $new=shift @new;
271 17         43 $new=$self->_convert_initial_value($new);
272 17 100       202 confess "Trying to store multiple values in single-valued field $key" if @new;
273 16 50       35 confess "Trying to store reference in single-valued field $key" if ref $new;
274 16         32 $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     8756 @new=@{$new[0]} if @new==1 && 'ARRAY' eq ref $new[0];
  2271         3372  
279 2296 100       3516 confess "Trying to store reference in multi-valued field $key" if grep {ref($_)} @new;
  1126         2597  
280 2295 100       3792 if (my $unique=$self->unique) {
281 586         569 my($a,$b);
282 586         1078 for $a (@new) {
283 251 100       665 push(@$old,$a) unless grep {$b=$_; &$unique($a,$b)} @$old;
  283         379  
  283         556  
284             }} else {
285 1709         3628 push(@$old,@new);
286             }
287             } elsif (UNIVERSAL::isa($old,'Hash::AutoHash')) { # old Hash::AutoHash
288 42         69 @new=_flatten(@new);
289 42         96 while (@new>1) { # store initial values
290 52         295 my($key,$value)=splice @new,0,2; # shift 1st two elements
291 52         273 $old->$key($value); # store using hash notation
292             }
293             } else { # old anything else.
294 3         10 $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   2269 if (@_==1) {
312 595 100       2433 return ('ARRAY' eq ref $_[0])? @{$_[0]}: ('HASH' eq ref $_[0])? %{$_[0]}: @_;
  5 100       17  
  587         2501  
313             }
314 247         636 @_;
315             }
316              
317             1;
318              
319             __END__