File Coverage

blib/lib/Tie/UnionHash.pm
Criterion Covered Total %
statement 85 86 98.8
branch 23 24 95.8
condition n/a
subroutine 17 17 100.0
pod 0 2 0.0
total 125 129 96.9


line stmt bran cond sub pod time code
1             package Tie::UnionHash;
2              
3             =head1 NAME
4              
5             Tie::UnionHash - Union hashes. Make changes to the last hash in arguments ( depend on option ).
6              
7             =head1 SYNOPSIS
8              
9             use Tie::UnionHash;
10            
11             tie %uhash, 'Tie::UnionHash', \%hash1ro, \%hash2rw;
12            
13             tie %hashu, 'Tie::UnionHash', \%hash1, \%hash2, 'freeze_keys' ;
14              
15             =head1 DESCRIPTION
16              
17             Tie::UnionHash - Merge multiple hashes into a one hash. Make changes only to the last hash in arguments, unless used option I.
18              
19             Tie::UnionHash can handle anything that looks like a hash; just give it a reference as one of the additional arguments to tie(). This includes other tied hashes, so you can include DB and DBM files as data sources for a union hash. If given a plain name instead of a reference, it will use as option.
20              
21             UnionHash correctly distinguish deleted keys.
22              
23             my %hash1 = ( 1 => 1, 3 => 3 );
24             my %hash2 = ( 2 => 2, 3 => 3 );
25             my %hashu;
26             tie %hashu, 'Tie::UnionHash', \%hash1, \%hash2;
27             # keys %hashu is [ '1', '2', '3' ]
28             $hashu{3} = 4 #change %hash2;
29             delete $hashu{3} #change %hash2 and track deleted keys
30             exist $hashu{3} # false, but exists in read only hashes
31              
32             Option I will change mode to readonly keys in hashes, except last hash in arguments.
33              
34             my %hash1 = ( 1 => 1, 3 => 3 );
35             my %hash2 = ( 2 => 2, 3 => 3 );
36             my %hashu;
37             tie %hashu, 'Tie::UnionHash', \%hash1, \%hash2, 'freeze_keys' ;
38             $hashu{3} = 4 #make changes to %hash1 : ( 1 => 1, 3 => 4 );
39             $hashu{NEW_KEY} = 1 # make changes to %hash2 :
40             #( 2 => 2, 3 => 3, NEW_KEY =>1 );;
41            
42            
43             =cut
44              
45 2     2   77500 use strict;
  2         4  
  2         72  
46 2     2   12 use warnings;
  2         4  
  2         56  
47 2     2   10 use strict;
  2         8  
  2         50  
48 2     2   9 use Carp;
  2         3  
  2         111  
49 2     2   9 use Data::Dumper;
  2         4  
  2         166  
50             require Tie::Hash;
51             @Tie::UnionHash::ISA = qw(Tie::StdHash);
52             $Tie::UnionHash::VERSION = '0.02';
53              
54             ### install get/set accessors for this object.
55             for my $key (qw( _orig_hashes _for_write __temp_array _opt _deleted_keys)) {
56 2     2   10 no strict 'refs';
  2         2  
  2         1710  
57             *{ __PACKAGE__ . "::$key" } = sub {
58 381     381   366 my $self = shift;
59 381 100       667 $self->{$key} = $_[0] if @_;
60 381         904 return $self->{$key};
61             }
62             }
63              
64             sub new {
65 2     2 0 4 my $class = shift;
66 2 50       8 $class = ref $class if ref $class;
67 2         8 my $self = bless( {}, $class );
68 2         4 my %opt = ();
69 2         4 my @hashes = ();
70 2         6 foreach my $par (@_) {
71 5 100       15 if ( ref($par) ) {
72 4         6 push @hashes, $par;
73 4         8 next;
74             }
75 1         4 $opt{$par} = 1;
76             }
77 2         12 $self->_for_write( $hashes[-1] );
78 2         9 $self->_orig_hashes( \@hashes );
79 2         8 $self->_opt( \%opt );
80 2         6 $self->_deleted_keys( {} );
81 2         7 $self;
82             }
83              
84             #delete keys only from _for_write hashe!
85             sub DELETE {
86 10     10   2006 my ( $self, $key ) = @_;
87 10         20 delete $self->_for_write->{$key};
88 10 100       19 $self->_deleted_keys->{$key}++ unless $self->_opt->{freeze_keys};
89             }
90              
91             sub STORE {
92 5     5   12 my ( $self, $key, $val ) = @_;
93 5         13 my $hashes = $self->_orig_hashes;
94              
95             #restore key from deleted
96 5         567 delete $self->_deleted_keys->{$key};
97              
98             #set changes only in rw hash
99 5 100       11 return $self->_for_write->{$key} = $val unless $self->_opt->{freeze_keys};
100 2         3 foreach my $hash (@$hashes) {
101 3 100       8 next unless exists $hash->{$key};
102 1         3 return $hash->{$key} = $val;
103             }
104 1         3 $self->_for_write->{$key} = $val;
105             }
106              
107             sub FETCH {
108 30     30   122 my ( $self, $key ) = @_;
109 30         44 my $hashes = $self->_orig_hashes;
110 30 100       62 unless ( $self->_opt->{freeze_keys} ) {
111              
112             #skip deleted keys
113 16 100       23 return if exists $self->_deleted_keys->{$key};
114 15 100       25 return $self->_for_write->{$key} if exists $self->_for_write->{$key};
115             }
116 18         24 foreach my $hash (@$hashes) {
117 24 100       53 next unless exists $hash->{$key};
118 18         47 return $hash->{$key};
119             }
120 0         0 return;
121             }
122              
123             sub GetKeys {
124 49     49 0 51 my $self = shift;
125 49         74 my $hashes = $self->_orig_hashes;
126 49         55 my %uniq;
127 49         65 foreach my $hash (@$hashes) {
128 98         385 $uniq{$_}++ for keys %$hash;
129             }
130              
131             #skip deleted keys
132 49 100       89 unless ( $self->_opt->{freeze_keys} ) {
133 27         54 my $del_keys_map = $self->_deleted_keys;
134 27         57 for ( keys %uniq ) {
135 105 100       214 delete $uniq{$_}
136             if exists $del_keys_map->{$_};
137             }
138             }
139 49         239 return [ keys %uniq ];
140             }
141              
142 2     2   42 sub TIEHASH { shift; return __PACKAGE__->new(@_) }
  2         16  
143              
144             sub FIRSTKEY {
145 13     13   5379 my ($self) = @_;
146 13         15 $self->__temp_array( [ sort { $a cmp $b } @{ $self->GetKeys() } ] );
  45         94  
  13         28  
147 13         25 shift( @{ $self->__temp_array() } );
  13         21  
148             }
149              
150             sub NEXTKEY {
151 42     42   78 my ( $self, $key ) = @_;
152 42         39 shift( @{ $self->__temp_array() } );
  42         67  
153             }
154              
155             sub EXISTS {
156 34     34   924 my ( $self, $key ) = @_;
157 34         54 my $hashes = $self->_orig_hashes;
158 34         49 my %tmp;
159 34         33 @tmp{ @{ $self->GetKeys } } = ();
  34         56  
160 34         131 return exists $tmp{$key};
161             }
162              
163             sub CLEAR {
164 2     2   1888 my $self = shift;
165 2         4 $self->DELETE($_) for @{ $self->GetKeys };
  2         17  
166             }
167              
168             1;
169             __END__