File Coverage

blib/lib/Data/COW.pm
Criterion Covered Total %
statement 97 164 59.1
branch 20 36 55.5
condition 6 18 33.3
subroutine 28 48 58.3
pod 0 6 0.0
total 151 272 55.5


line stmt bran cond sub pod time code
1             package Data::COW;
2              
3 1     1   37102 use 5.006001;
  1         3  
  1         41  
4 1     1   5 use strict;
  1         2  
  1         34  
5 1     1   5 no warnings;
  1         5  
  1         41  
6              
7 1     1   6 use Exporter;
  1         2  
  1         61  
8 1     1   6 use Scalar::Util qw;
  1         2  
  1         131  
9 1     1   1791 use overload (); # we're not overloading anything, but we'd like to
  1         1078  
  1         21  
10             # check if they're already implementing a value type
11              
12 1     1   6 use base 'Exporter';
  1         1  
  1         766  
13              
14             our @EXPORT = qw;
15              
16             our $VERSION = '0.02';
17              
18             sub tied_any {
19 13     13 0 15 my ($ref) = @_;
20 13 100       57 if (ref $ref) {
21 8 50       46 if (reftype($ref) eq 'SCALAR') {
    50          
    0          
22 0         0 tied $$ref;
23             }
24             elsif (reftype($ref) eq 'ARRAY') {
25 8         31 tied @$ref;
26             }
27             elsif (reftype($ref) eq 'HASH') {
28 0         0 tied %$ref;
29             }
30             }
31              
32             }
33              
34             sub cow_object {
35 11     11 0 14 my ($ref) = @_;
36 11         20 my $tied = tied_any $ref;
37 11 100 66     63 $tied && $tied->isa('Data::COW') && $tied;
38             }
39              
40             sub make_cow_ref {
41 3     3 0 21 my ($ref, $parent, $key) = @_;
42              
43 3         6 make_cow_ref_nocheck($ref, $parent, $key);
44             }
45              
46             sub make_temp_cow_ref {
47 11     11 0 18 my ($ref, $parent, $key) = @_;
48              
49 11 100       18 if (my $obj = cow_object $ref) {
50 3 50       9 if ($obj->{parent} == $parent) {
51 3         16 $ref;
52             }
53             else {
54 0         0 make_cow_ref_nocheck($ref, $parent, $key);
55             }
56             }
57             else {
58 8         15 make_cow_ref_nocheck($ref, $parent, $key);
59             }
60             }
61              
62             sub make_cow_ref_nocheck {
63 11     11 0 17 my ($ref, $parent, $key) = @_;
64              
65 11 100 33     40 if (ref $ref &&
      66        
66             # check if they already think they're a value type
67             !(overload::Overloaded($ref) && overload::Method($ref, '=')))
68             {
69 6         2895 my $ret;
70 6 50       31 if (reftype($ref) eq 'SCALAR') {
    50          
    0          
71 0         0 tie my $it => 'Data::COW::Scalar', $ref, $parent, $key;
72 0         0 $ret = \$it;
73             }
74             elsif (reftype($ref) eq 'ARRAY') {
75 6         80 tie my @it => 'Data::COW::Array', $ref, $parent, $key;
76 6         11 $ret = \@it;
77             }
78             elsif (reftype($ref) eq 'HASH') {
79 0         0 tie my %it => 'Data::COW::Hash', $ref, $parent, $key;
80 0         0 $ret = \%it;
81             }
82             else {
83             # code and glob are not aggregates that we can take control
84             # of, so punt and just return them like anything else
85 0         0 return $ref;
86             }
87            
88 6 50       20 if (blessed($ref)) {
89 0         0 bless $ret => blessed($ref);
90             }
91            
92 6         28 return $ret;
93             }
94             else {
95 5         19 return $ref;
96             }
97             }
98              
99             sub clone_using {
100 5     5 0 9 my ($self, $copier) = @_;
101              
102 5 100       16 return unless $self->{const};
103 3         6 my $old = $self->{ref};
104 3         6 my $new = $copier->($old);
105              
106 3 50       10 if (blessed $old) {
107 0         0 bless $new => blessed $old;
108             }
109 3 100       8 if ($self->{parent}) {
110 2         6 my $cnew = make_cow_ref $new, $self->{parent}, undef;
111 2         6 tied_any($cnew)->{const} = 0;
112 2         8 $self->{parent}->clone($self->{key} => $cnew);
113             }
114 3         6 $self->{ref} = $new;
115 3         5 $self->{const} = 0;
116             }
117              
118             package Data::COW::Scalar;
119              
120 1     1   873 use Tie::Scalar;
  1         562  
  1         24  
121 1     1   5 use base 'Tie::Scalar';
  1         2  
  1         96  
122 1     1   5 use base 'Data::COW';
  1         2  
  1         259  
123              
124             sub TIESCALAR {
125 0     0   0 my ($class, $ref, $parent, $key) = @_;
126 0   0     0 bless {
127             ref => $ref,
128             parent => $parent,
129             key => $key,
130             const => 1,
131             } => ref $class || $class;
132             }
133              
134             sub clone {
135 0     0   0 my ($self, $key, $value) = @_;
136              
137 0     0   0 $self->clone_using(sub { my $v = ${$_[0]}; \$v });
  0         0  
  0         0  
  0         0  
138 0 0       0 ${$self->{ref}} = $value if defined $key;
  0         0  
139             }
140              
141             sub FETCH {
142 0     0   0 my ($self) = @_;
143 0         0 Data::COW::make_temp_cow_ref(${$self->{ref}}, $self, 1);
  0         0  
144             }
145              
146             sub STORE {
147 0     0   0 my ($self, $value) = @_;
148 0         0 $self->clone(1 => $value);
149 0         0 $value;
150             }
151              
152             package Data::COW::Array;
153              
154 1     1   783 use Tie::Array;
  1         1365  
  1         29  
155 1     1   6 use base 'Tie::Array';
  1         2  
  1         72  
156 1     1   5 use base 'Data::COW';
  1         2  
  1         432  
157              
158             sub TIEARRAY {
159 6     6   12 my ($class, $ref, $parent, $key) = @_;
160 6   33     52 bless {
161             ref => $ref,
162             parent => $parent,
163             key => $key,
164             const => 1,
165             } => ref $class || $class;
166             }
167              
168             sub clone {
169 5     5   6 my ($self, $key, $value) = @_;
170 5     3   34 $self->clone_using(sub { [ @{$_[0]} ] });
  3         11  
  3         9  
171 5 50       25 $self->{ref}[$key] = $value if defined $key;
172             }
173              
174             sub FETCH {
175 11     11   432 my ($self, $key) = @_;
176 11         28 Data::COW::make_temp_cow_ref($self->{ref}[$key], $self, $key);
177             }
178              
179             sub STORE {
180 3     3   6 my ($self, $key, $value) = @_;
181 3         8 $self->clone($key => $value);
182 3         9 $value;
183             }
184              
185             sub FETCHSIZE {
186 1     1   6 my ($self) = @_;
187 1         1 scalar @{$self->{ref}};
  1         6  
188             }
189              
190             sub STORESIZE {
191 0     0     my ($self, $size) = @_;
192 0           $self->clone;
193 0           $#{$self->{ref}} = $size-1;
  0            
194             }
195              
196             sub DELETE {
197 0     0     my ($self, $key) = @_;
198 0           $self->clone;
199 0           delete $self->{ref}[$key];
200             }
201              
202             sub EXISTS {
203 0     0     my ($self, $key) = @_;
204 0           exists $self->{ref}[$key];
205             }
206              
207             package Data::COW::Hash;
208              
209 1     1   949 use Tie::Hash;
  1         899  
  1         26  
210 1     1   5 use base 'Tie::Hash';
  1         1  
  1         65  
211 1     1   5 use base 'Data::COW';
  1         2  
  1         552  
212              
213             sub TIEHASH {
214 0     0     my ($class, $ref, $parent, $key) = @_;
215 0   0       bless {
216             ref => $ref,
217             parent => $parent,
218             key => $key,
219             const => 1,
220             } => ref $class || $class;
221             }
222              
223             sub clone {
224 0     0     my ($self, $key, $value) = @_;
225             $self->clone_using(sub {
226 0     0     my $ret = { %{$_[0]} };
  0            
227 0           $ret;
228 0           });
229 0 0         $self->{ref}{$key} = $value if defined $key;
230             }
231              
232             sub FETCH {
233 0     0     my ($self, $key) = @_;
234 0           Data::COW::make_temp_cow_ref($self->{ref}{$key}, $self, $key);
235             }
236              
237             sub STORE {
238 0     0     my ($self, $key, $value) = @_;
239 0           $self->clone($key => $value);
240 0           $value;
241             }
242              
243             sub EXISTS {
244 0     0     my ($self, $key) = @_;
245 0           exists $self->{ref}{$key};
246             }
247              
248             sub DELETE {
249 0     0     my ($self, $key) = @_;
250 0           $self->clone;
251 0           delete $self->{ref}{$key};
252             }
253              
254             sub CLEAR {
255 0     0     my ($self) = @_;
256 0     0     $self->clone_using(sub { {} });
  0            
257 0           ();
258             }
259              
260             sub FIRSTKEY {
261 0     0     my ($self) = @_;
262 0           my $a = keys %{$self->{ref}}; # reset iterator
  0            
263 0           each %{$self->{ref}};
  0            
264             }
265              
266             sub NEXTKEY {
267 0     0     my ($self) = @_;
268 0           each %{$self->{ref}};
  0            
269             }
270              
271             sub SCALAR {
272 0     0     my ($self) = @_;
273 0           scalar %{$self->{ref}};
  0            
274             }
275              
276             1;
277              
278             =head1 NAME
279              
280             Data::COW - clone deep data structures copy-on-write
281              
282             =head1 SYNOPSIS
283              
284             use Data::COW;
285              
286             my $array = [ 0, 1, 2 ];
287             my $copy = make_cow_ref $array;
288              
289             push @$array, 3;
290             # $copy->[3] is 3
291             push @$copy, 4;
292             # $array->[4] is not defined (and doesn't even exist)
293             # $copy is a real copy now
294             push @$array, 5;
295             # $copy is unaffected
296              
297             =head1 DESCRIPTION
298              
299             Data::COW makes copies of data structures copy-on-write, or "lazily".
300             So if you have a data structure that takes up ten megs of memory, it
301             doesn't take ten megs to copy it. Even if you change part of it,
302             Data::COW only copies the parts that need to be copied in order to
303             reflect the change.
304              
305             Data::COW exports one function: C. This takes a reference
306             and returns a copy-on-write reference to it. If you don't want this
307             in your namespace, and you want to use it as C,
308             use the module like this:
309              
310             use Data::COW ();
311              
312             Data::COW won't be able to copy filehandles or glob references. But how
313             do you change those anyway? It's also probably a bad idea to give it
314             objects that refer to XS internal state without providing a value type
315             interface. Also, don't use stringified references from this data
316             structure: they're different each time you access them!
317              
318             =head1 SEE ALSO
319              
320             L
321              
322             =head1 AUTHOR
323              
324             Luke Palmer
325              
326             =head1 COPYRIGHT
327              
328             Copyright (C) 2005 by Luke Palmer
329              
330             This library is free software; you can redistribute it and/or modify it under
331             the same terms as Perl itself, either Perl version 5.8.3 or, at your option,
332             any later version of Perl 5 you may have available.