File Coverage

blib/lib/Alter.pm
Criterion Covered Total %
statement 87 87 100.0
branch 29 34 85.2
condition 9 14 64.2
subroutine 19 19 100.0
pod 0 2 0.0
total 144 156 92.3


line stmt bran cond sub pod time code
1             package Alter;
2 3     3   97466 use 5.008000;
  3         14  
  3         134  
3 3     3   20 use strict; use warnings;
  3     3   6  
  3         117  
  3         16  
  3         10  
  3         548  
4              
5             our $VERSION = '0.07';
6              
7             our %EXPORT_TAGS = (
8             all => [ qw(
9             alter ego
10             STORABLE_freeze STORABLE_attach STORABLE_thaw
11             Dumper
12             ) ],
13             );
14             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
15              
16             # for re-exportation
17             *STORABLE_freeze = \ &Alter::Storable::STORABLE_freeze;
18             *STORABLE_thaw = \ &Alter::Storable::STORABLE_thaw;
19             *STORABLE_attach = \ &Alter::Storable::STORABLE_attach;
20             *Dumper= \ &Alter::Dumper::Dumper;
21              
22             eval {
23             die "Pure Perl requested" if $ENV{ PERL_ALTER_NO_XS}; # fake load failure
24 3     3   18 no warnings 'redefine';
  3         96  
  3         336  
25             require XSLoader;
26             XSLoader::load('Alter', $VERSION);
27 21     21   20973 *is_xs = sub { 1 };
28             };
29             if ( $@ ) {
30             # Fallback to pure perl implementation
31 3     3   17 no warnings 'redefine';
  3         5  
  3         210  
32             require Alter::AlterXS_in_perl if $@;
33             *is_xs = sub { 0 };
34             }
35              
36             ### Import/Export
37              
38             # Types supported for autovivification
39 3     3   18 use Scalar::Util qw( reftype);
  3         6  
  3         1678  
40             my %ref_tab = (
41             NOAUTO => 'NOAUTO',
42             map +( reftype( $_) => $_) => (
43             \ do { my $o }, # scalar
44             [], # array
45             {}, # hash
46             ),
47             );
48              
49             sub import {
50 19     19   34744 require Exporter;
51 19         40 my $class = shift;
52 19         38 my $client = caller;
53 19         35 my $storable = my $dumper = my $destroy = 1;
54 19         52 for ( 1 .. @_ ) {
55 36         61 my $arg = shift;
56 36 100       103 $storable = 0, next if $arg eq '-storable';
57 35 100       77 $dumper = 0, next if $arg eq '-dumper';
58 34 50       307 $destroy = 0, next if $arg eq '-destroy';
59 34 100       84 $storable = 0 if $arg =~ /^STORABLE_/;
60 34 100       72 $dumper = 0 if $arg eq 'Dumper';
61 34 100       88 my $type = ref( $arg) ? $arg : $ref_tab{ $arg};
62 34 100 100     203 if ( $type && ref( $type) && $ref_tab{ reftype $type} ) {
    100 66        
      66        
63 5         33 _set_class_type( $client, $type);
64 5         16 next;
65             } elsif ( $type and $type eq 'NOAUTO' ) {
66 1         4 _set_class_type( $client, undef); # delete entry
67 1         4 next;
68             }
69 28         79 push @_, $arg; # hand down to Exporter::import()
70             }
71 19 100       595 _add_base( $client, 'Alter::Storable') if $storable;
72 19 100       69 _add_base( $client, 'Alter::Dumper') if $dumper;
73 19 50 33     50 _add_base( $client, 'Alter::Destructor') if !is_xs() && $destroy;
74 19         40 unshift @_, $class;
75 19   50     2654 goto Exporter->can( 'import') || die "Exporter can't import???";
76             }
77              
78             sub _add_base {
79 31     31   47 my ( $client, $base) = @_;
80 31 100       285 return if $client->isa( $base);
81 3     3   19 no strict 'refs';
  3         6  
  3         195  
82 24         27 push @{ join '::' => $client, 'ISA' }, $base;
  24         310  
83             }
84              
85             ### Serialization support: ->image and ->reify
86              
87             # Key to use for object body in image (different from any class name)
88 3     3   168 use constant BODY => '(body)';
  3         8  
  3         1170  
89              
90             # create a hash image of an object that contains the body and
91             # corona data
92             sub image {
93 3     3 0 7 my $obj = shift;
94             +{
95 3         35 BODY() => $obj,
96 3         5 %{ corona( $obj) }, # shallow copy
97             };
98             }
99              
100             # recreate the original object from an image. When called as a
101             # class method, take the object from the "(body)" entry in image
102             # (the class is ignored). Called as an object method, re-model
103             # the given object (whose data is lost) to match the image. In
104             # this case, the types of the given object and the "(body)" entry
105             # must match, or else... Also, the ref type must be supported
106             # ("CODE" isn't).
107             sub reify {
108 2     2 0 72 my $obj = shift;
109 2         5 my $im = shift;
110 2 100       11 if ( ref $obj ) {
111 1         3 my $orig = delete $im->{ BODY()};
112 1         6 _transfer_content( $orig, $obj);
113             } else {
114 1         4 $obj = delete $im->{ BODY()};
115             }
116 2         8 %{ corona( $obj)} = %$im;
  2         11  
117 2         18 $obj;
118             }
119              
120             my %trans_tab = (
121             SCALAR => sub { ${ $_[ 1] } = ${ $_[ 0] } },
122             ARRAY => sub { @{ $_[ 1] } = @{ $_[ 0] } },
123             HASH => sub { %{ $_[ 1] } = %{ $_[ 0] } },
124             GLOB => sub { *{ $_[ 1] } = *{ $_[ 0] } },
125             );
126              
127 3     3   19 use Carp;
  3         6  
  3         1621  
128             sub _transfer_content {
129 1     1   3 my ( $from, $to) = @_;
130 1         7 my $type = reftype $from;
131 1 50       8 croak "Incompatible types in STORABLE_thaw" unless
132             $type eq reftype $to;
133 1 50       5 croak "Unsupported type '$type' in STORABLE_thaw" unless
134             my $trans = $trans_tab{ $type};
135 1         5 $trans->( $_[ 0], $_[ 1]); # may change $_[ 1] ($to)
136 1         13 $_[ 1];
137             }
138              
139             ### Data::Dumper support (for viewing only)
140             {
141             package Alter::Dumper;
142              
143             # return a viewable string containing the object information
144             sub Dumper {
145 1     1   6728 my $obj = shift;
146 1         12 require Data::Dumper;
147 1         4 local $Data::Dumper::Purity = 1;
148 1         6 Data::Dumper::Dumper( $obj->Alter::image);
149             }
150             }
151              
152             ### Storable support
153             {
154             package Alter::Storable;
155              
156             my $running; # indicate if the call is (indirectly) from ourselves
157             sub STORABLE_freeze {
158 4     4   17816 my ( $obj, $cloning) = @_;
159 4 50       16 return if $cloning;
160 4 100       323 return unless $running = !$running; # return if $running was true
161             # $running now true, preventing recursion
162 2         17 Storable::freeze( $obj->Alter::image);
163             }
164              
165             # recognized (and preferred) by Storable 2.15+, (Perl v5.8.8)
166             # ignored by earlier versions
167             sub STORABLE_attach {
168 1     1   79 my ( $class, $cloning, $ser) = @_;
169 1         3 ++ our $attaching; # used by t/*.t, not needed for anything else
170 1         6 $class->Alter::reify( Storable::thaw( $ser));
171             }
172              
173             # recognized by all versions of Storable
174             # incidentally, the code is equivalent to STORABLE_attach
175             sub STORABLE_thaw {
176 1     1   78 my ( $obj, $cloning, $ser) = @_;
177 1         2 ++ our $thawing; # used by t/*.t, not needed for anything else
178 1         5 $obj->Alter::reify( Storable::thaw( $ser));
179             }
180             }
181              
182             1;
183             __END__