File Coverage

blib/lib/Tie/Proxy/Changes.pm
Criterion Covered Total %
statement 104 217 47.9
branch 30 64 46.8
condition 8 12 66.6
subroutine 18 33 54.5
pod 1 1 100.0
total 161 327 49.2


line stmt bran cond sub pod time code
1             #############################################################################
2             #Changes.pm
3             #Last Change: 2009-02-18
4             #Copyright (c) 2008 Marc-Seabstian "Maluku" Lucksch
5             #Version 0.1
6             ####################
7             #Changes.pm is published under the terms of the MIT license, which
8             #basically means "Do with it whatever you want". For more information, see the
9             #license.txt file that should be enclosed with plasma distributions. A copy of
10             #the license is (at the time of this writing) also available at
11             #http://www.opensource.org/licenses/mit-license.php .
12             #############################################################################
13            
14             package Tie::Proxy::Changes;
15 1     1   2517 use strict;
  1         3  
  1         39  
16 1     1   6 use warnings;
  1         3  
  1         36  
17 1     1   15 use Carp qw/croak/;
  1         2  
  1         124  
18             require Scalar::Util;
19             use overload
20 1         14 '""'=> \&_getbool,
21             'bool'=>\&_getbool,
22             '%{}'=>\&_gethash,
23             '@{}'=>\&_getarray,
24             '${}'=>\&_getscalar,
25 1     1   6 'nomethod'=>\&_getbool;
  1         2  
26            
27             our $VERSION = 0.2;
28            
29            
30             #For thread safety
31            
32             my %REGISTRY;
33            
34             # Define the data store fields
35             # We have to make this an inside-out-object so it can be used by deref in any
36             # call, without killing overload.
37            
38             my %caller_of;
39             my %index_of;
40             my %data_of;
41             my %tiearray_of;
42             my %tiehash_of;
43             my %tiescalar_of;
44            
45             # Helper method for inside-out objecst:
46            
47             sub _id {
48 54     54   139 return Scalar::Util::refaddr($_[0]);
49             }
50            
51             # Create a new Tie::Proxy::Changes with optional data.
52             sub new {
53 15     15 1 5058 my $class=shift;
54 15         19 my $self=bless \do{ my $anon; },$class;
  15         48  
55            
56             # Safe it into the registry for thread stuff:
57            
58 15         36 my $id=_id($self);
59 15         61 Scalar::Util::weaken($REGISTRY{$id}=$self);
60            
61 15         27 $caller_of{$id}=shift;
62            
63 15         20 my $index=shift;
64 15 100       49 $index_of{$id}=$index if defined $index;
65             #my $self=[$calling_obj,$index];
66            
67             # Get the current state of the value, if it is there.
68 15         18 my $data=shift;
69 15 100       35 $data_of{$id}=$data if $data;
70            
71 15         48 return $self;
72             }
73            
74             # Access this object as a hashref.
75             sub _gethash {
76 10     10   78 my $self=shift;
77 10         20 my $id=_id($self);
78            
79             # Return the stored access if it is already there.
80 10 100       33 return $tiehash_of{$id} if $tiehash_of{$id};
81            
82             # Check the existing data or create it (if not there)
83 9 50 66     57 croak "Can't use an ".ref $data_of{$id}." as a hash"
84             if exists $data_of{$id}
85             and Scalar::Util::reftype($data_of{$id}) ne "HASH";
86 9 100       25 $data_of{$id}={} unless $data_of{$id};
87            
88             # Tie myself as a hash.
89 9         17 my %h=();
90 9         37 tie %h,ref $self,$self;
91 9         12 my $x=\%h;
92            
93             # Store the tied object for faster access.
94 9         18 $tiehash_of{$id}=$x;
95            
96 9         53 return $x;
97             }
98            
99             # Access this object as an arrayref.
100             sub _getarray {
101 3     3   13 my $self=shift;
102 3         7 my $id=_id($self);
103            
104             # Return the stored access if it is already there.
105 3 50       10 return $tiearray_of{$id} if $tiearray_of{$id};
106            
107             # Check the existing data or create it (if not there)
108 3 50 66     21 croak "Can't use ".ref $data_of{$id}." as an array"
109             if exists $data_of{$id}
110             and Scalar::Util::reftype($data_of{$id}) ne "ARRAY";
111 3 100       8 $data_of{$id}=[] unless $data_of{$id};
112            
113             # Tie myself as an array.
114 3         7 my @a=();
115 3         13 tie @a,ref $self,$self;
116 3         5 my $x=\@a;
117            
118             # Store the tied object for faster access.
119 3         7 $tiearray_of{$id}=$x;
120            
121 3         15 return $x;
122             }
123            
124             # Access this as a scalar ref.
125            
126             sub _getscalar {
127 2     2   4 my $self=shift;
128 2         4 my $id=_id($self);
129            
130             # Return the stored access if it is already there.
131 2 50       6 return $tiescalar_of{$id} if $tiescalar_of{$id};
132            
133             # Check the existing data or create it (if not there)
134 2 50 66     20 croak "Can't use ".ref $data_of{$id}." as a scalarref"
      66        
135             if exists $data_of{$id}
136             and Scalar::Util::reftype($data_of{$id}) ne "REF"
137             and Scalar::Util::reftype($data_of{$id}) ne "SCALAR"
138             ;
139 2 50       5 $data_of{$id}=\do {my $d;} unless exists $data_of{$id};
  0         0  
140            
141             # Tie myself as an array.
142 2         2 my $s;
143 2         9 tie $s,ref $self,$self;
144 2         3 my $x=\$s;
145            
146             # Store the tied object for faster access.
147 2         4 $tiescalar_of{$id}=$x;
148            
149 2         7 return $x;
150             }
151            
152             # Test for the boolean value
153             sub _getbool {
154 1     1   5 my $self=shift;
155 1         3 my $id=_id($self);
156            
157             # Test for data, return the size of array or hash data.
158 1 50       5 if ($data_of{$id}) {
159 1 50       4 if (Scalar::Util::reftype($data_of{$id})) {
160 0 0       0 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
    0          
    0          
    0          
161 0         0 return scalar %{$data_of{$id}};
  0         0  
162             }
163             elsif (Scalar::Util::reftype($data_of{$id}) eq "ARRAY") {
164 0         0 return scalar @{$data_of{$id}};
  0         0  
165             }
166             elsif (Scalar::Util::reftype($data_of{$id}) eq "REF") {
167 0         0 return ${$data_of{$id}};
  0         0  
168             }
169             elsif (Scalar::Util::reftype($data_of{$id}) eq "SCALAR") {
170 0         0 return ${$data_of{$id}};
  0         0  
171             }
172             }
173             # Return other data, if it's not an array or a hash
174 1         3 return $data_of{$id};
175             }
176            
177             # Empty object is always false (Happens during autovivify)
178 0         0 return 0;
179             }
180            
181             # Helper to set the right params for STORE
182             #
183             sub _upper {
184 14     14   17 my $id=shift;
185 14 50       29 die "upper has to be called as a list" unless wantarray;
186 14 100       37 if (exists $index_of{$id}) {
187 13         68 return ($index_of{$id},$data_of{$id});
188             }
189 1         3 return $data_of{$id};
190             }
191            
192             # To understand this, reading of perltie is required.
193            
194             sub TIEHASH {
195 9     9   12 my $class=shift;
196 9         25 return shift;
197             }
198             sub TIEARRAY {
199 3     3   7 my $class=shift;
200 3         7 return shift;
201             }
202            
203             sub TIESCALAR {
204 2     2   5 my $class=shift;
205 2         5 return shift;
206             }
207            
208             sub STORE {
209 13     13   19 my $self=shift;
210 13         30 my $id=_id($self);
211 13         23 my $key=shift;
212 13         16 my $value=shift;
213            
214             # Choose the right operating method, since STORE can be called on both
215             # arrays and hashes
216 13 100       657 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
    100          
217 9         40 $data_of{$id}->{$key}=$value;
218             }
219             elsif (Scalar::Util::reftype($data_of{$id}) eq "ARRAY") {
220 2         6 $data_of{$id}->[$key]=$value;
221             }
222             else {
223 2         7 ${$data_of{$id}}=$key;
  2         4  
224             }
225             # Content has changed, call STORE of the emitting object/tie.
226            
227 13         30 $caller_of{$id}->STORE(_upper($id));
228 13         2816 return;
229             }
230             sub FETCH {
231 8     8   10 my $self=shift;
232 8         15 my $id=_id($self);
233 8         12 my $key=shift;
234            
235             # Choose the right operationg method, FETCH is also implemented for both
236             # arrays and hashes.
237             # This also creates a new ChangeProxy, so it can track the changes of the
238             # data of this object as well. The STORE calls are stacking till they
239             # reach the emitting object.
240 8 100       33 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
    50          
241 7 100       32 return __PACKAGE__->new($self,$key,$data_of{$id}->{$key})
242             if $data_of{$id}->{$key};
243             }
244             elsif (Scalar::Util::reftype($data_of{$id}) eq "ARRAY") {
245 0 0       0 return __PACKAGE__->new($self,$key,$data_of{$id}->[$key])
246             if $data_of{$id}->[$key];
247             }
248             else {
249 1         3 return __PACKAGE__->new($self,$key,${$data_of{$id}})
  1         4  
250             }
251            
252             # Also return an empty ChangeProxy on unknown keys or indices, so
253             # autovivify calls are tracked as well. The object will play empty/undef
254             # in bool context, so it works for both testing and autovivification,
255             # since there is no way to distinguish them from the FETCH call.
256 2         5 return __PACKAGE__->new($self,$key);
257             }
258            
259             # This implements the rest of the tie interface, nothing new here, they just
260             # call STORE on every change to proxy them as well.
261            
262             sub FIRSTKEY {
263 0     0   0 my $self=shift;
264 0         0 my $id=_id($self);
265 0         0 my $a = scalar keys %{$data_of{$id}}; each %{$data_of{$id}}
  0         0  
  0         0  
  0         0  
266             }
267             sub NEXTKEY {
268 0     0   0 my $self=shift;
269 0         0 my $id=_id($self);
270 0         0 each %{$data_of{$id}}
  0         0  
271             }
272             sub EXISTS {
273 0     0   0 my $self=shift;
274 0         0 my $id=_id($self);
275 0         0 my $key=shift;
276 0 0       0 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
277 0         0 return exists $data_of{$id}->{$key};
278             }
279             else {
280 0         0 return exists $data_of{$id}->{$key};
281             }
282             }
283             sub DELETE {
284 0     0   0 my $self=shift;
285 0         0 my $id=_id($self);
286 0         0 my $key=shift;
287 0 0       0 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
288 0         0 delete $data_of{$id}->{$key};
289             }
290             else {
291 0         0 delete $data_of{$id}->{$key};
292             }
293 0         0 $caller_of{$id}->STORE(_upper($id));
294             }
295             sub CLEAR {
296 0     0   0 my $self=shift;
297 0         0 my $id=_id($self);
298 0 0       0 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
299 0         0 %{$data_of{$id}}=();
  0         0  
300             }
301             else {
302 0         0 @{$data_of{$id}}=()
  0         0  
303             }
304 0         0 $caller_of{$id}->STORE(_upper($id));
305             }
306             sub SCALAR {
307 0     0   0 my $self=shift;
308 0         0 my $id=_id($self);
309 0 0       0 if (Scalar::Util::reftype($data_of{$id}) eq "HASH") {
310 0         0 return scalar %{$data_of{$id}};
  0         0  
311             }
312             else {
313 0         0 return scalar @{$data_of{$id}};
  0         0  
314             }
315             }
316            
317             sub FETCHSIZE {
318 0     0   0 my $self=shift;
319 0         0 my $id=_id($self);
320 0         0 scalar @{$data_of{$id}};
  0         0  
321             }
322             sub STORESIZE {
323 0     0   0 my $self=shift;
324 0         0 my $id=_id($self);
325 0         0 $#{$data_of{$id}} = $_[0]-1;
  0         0  
326 0         0 $caller_of{$id}->STORE(_upper($id));
327             }
328             sub POP {
329 0     0   0 my $self=shift;
330 0         0 my $id=_id($self);
331 0         0 my $e=pop(@{$data_of{$id}});
  0         0  
332 0         0 $caller_of{$id}->STORE(_upper($id));
333 0         0 return $e;
334             }
335             sub PUSH {
336 1     1   1 my $self=shift;
337 1         3 my $id=_id($self);
338 1         2 push(@{$data_of{$id}},@_);
  1         3  
339 1         4 $caller_of{$id}->STORE(_upper($id));
340 1         489 return;
341             }
342             sub SHIFT {
343 0     0   0 my $self=shift;
344 0         0 my $id=_id($self);
345 0         0 my $e=shift(@{$data_of{$id}});
  0         0  
346 0         0 $caller_of{$id}->STORE(_upper($id));
347 0         0 return $e;
348             }
349             sub UNSHIFT {
350 0     0   0 my $self=shift;
351 0         0 my $id=_id($self);
352 0         0 unshift(@{$data_of{$id}},@_);
  0         0  
353 0         0 $caller_of{$id}->STORE(_upper($id));
354 0         0 return; }
355            
356             sub SPLICE {
357 0     0   0 my $self=shift;
358 0         0 my $id=_id($self);
359 0         0 my $sz = scalar @{$data_of{$id}};
  0         0  
360 0 0       0 my $off = @_ ? shift : 0;
361 0 0       0 $off += $sz if $off < 0;
362 0 0       0 my $len = @_ ? shift : $sz-$off;
363 0         0 my @rem=splice(@{$data_of{$id}},$off,$len,@_);
  0         0  
364 0         0 $caller_of{$id}->STORE(_upper($id));
365 0         0 return @rem;
366             }
367            
368             # Idea from http://www.perlmonks.org/?node_id=483162
369             # Method to clone on ithreads
370             sub CLONE {
371 0     0   0 for my $old_reference ( keys %REGISTRY ) {
372 0         0 my $object = $REGISTRY{ $old_reference };
373 0         0 my $new_reference = Scalar::Util::refaddr($object);
374            
375 0         0 $caller_of{$new_reference}=$caller_of{$old_reference};
376 0         0 delete $caller_of{$old_reference};
377 0         0 $index_of{$new_reference}=$index_of{$old_reference};
378 0         0 delete $index_of{$old_reference};
379 0         0 $data_of{$new_reference}=$data_of{$old_reference};
380 0         0 delete $data_of{$old_reference};
381 0         0 $tiearray_of{$new_reference}=$tiearray_of{$old_reference};
382 0         0 delete $tiearray_of{$old_reference};
383 0         0 $tiehash_of{$new_reference}=$tiehash_of{$old_reference};
384 0         0 delete $tiehash_of{$old_reference};
385 0         0 $tiescalar_of{$new_reference}=$tiescalar_of{$old_reference};
386 0         0 delete $tiescalar_of{$old_reference};
387            
388 0         0 Scalar::Util::weaken(
389             $REGISTRY{ $new_reference } = $REGISTRY{ $old_reference }
390             );
391 0         0 delete $REGISTRY{ $old_reference };
392            
393             }
394             }
395            
396             # DESTROY, needed for inside-out objects:
397             sub DESTROY {
398 1     1   8 my $self=shift;
399 1         3 my $id=_id($self);
400            
401 1         5 delete $REGISTRY{$id};
402 1         2 delete $caller_of{$id};
403 1         2 delete $index_of{$id};
404 1         9 delete $data_of{$id};
405 1         2 delete $tiearray_of{$id};
406 1         2 delete $tiehash_of{$id};
407 1         23 delete $tiescalar_of{$id};
408             }
409            
410             sub UNTIE {
411 0     0     my $self=shift;
412 0           my $id=_id($self);
413            
414 0           delete $tiearray_of{$id};
415 0           delete $tiehash_of{$id};
416 0           delete $tiescalar_of{$id};
417             }
418            
419             #Retrieve the internal stuff, for debug and maybe hacks :)
420             sub _data {
421 0     0     my $self=shift;
422 0           my $id=_id($self);
423             return {
424 0           object=>$caller_of{$id},
425             key=>$index_of{$id},
426             data=>$data_of{$id},
427             tied_array=>$tiearray_of{$id},
428             tied_hash=>$tiehash_of{$id},
429             tied_scalar=>$tiescalar_of{$id},
430             };
431             }
432            
433            
434             1;
435            
436             __END__