File Coverage

blib/lib/Class/Std/Storable.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Class::Std::Storable;
2              
3 2     2   48359 use version; $VERSION = qv('0.0.1');
  2         8704  
  2         13  
4 2     2   169 use strict;
  2         5  
  2         56  
5 2     2   11 use warnings;
  2         7  
  2         59  
6 2     2   2452 use Class::Std; #get subs from parent to export
  0            
  0            
7             use Carp;
8              
9             #hold attributes by package
10             my %attributes_of;
11              
12             my @exported_subs = qw(
13             new
14             ident
15             DESTROY
16             MODIFY_HASH_ATTRIBUTES
17             MODIFY_CODE_ATTRIBUTES
18             AUTOLOAD
19             _DUMP
20             STORABLE_freeze
21             STORABLE_thaw
22             );
23              
24             sub import {
25             no strict 'refs';
26             for my $sub ( @exported_subs ) {
27             *{ caller() . '::' . $sub } = \&{$sub};
28             }
29             }
30              
31             #NOTE: this subroutine should override the one that's imported
32             #by the "use Class::Std" above.
33             {
34             my $old_sub = \&Class::Std::MODIFY_HASH_ATTRIBUTES;
35             my %positional_arg_of;
36             my $new_sub = sub {
37             my ($package, $referent, @attrs) = @_;
38             my @return_attrs = $old_sub->(@_);
39              
40             for my $attr (@attrs) {
41             next if $attr !~ m/\A ATTRS? \s* (?:[(] (.*) [)] )? \z/xms;
42             my $name;
43             #we have a backup if no name is given for the attribute.
44             $positional_arg_of{$package} ||= "__Positional_0001";
45             #but we would prefer to know the argument as the class does.
46             if (my $config = $1) {
47             $name = Class::Std::_extract_init_arg($config)
48             || Class::Std::_extract_get($config)
49             || Class::Std::_extract_set($config);
50             }
51             $name ||= $positional_arg_of{$package}++;
52             push @{$attributes_of{$package}}, {
53             ref => $referent,
54             name => $name,
55             };
56             }
57             return @return_attrs;
58             };
59              
60             no warnings; #or this complains about redefining sub
61             *MODIFY_HASH_ATTRIBUTES = $new_sub;
62             };
63              
64             sub STORABLE_freeze {
65             #croak "must be called from Storable" unless caller eq 'Storable';
66             #unfortunately, Storable never appears on the call stack.
67             my($self, $cloning) = @_;
68             $self->STORABLE_freeze_pre($cloning)
69             if UNIVERSAL::can($self, "STORABLE_freeze_pre");
70             my $id = ident($self);
71             require Storable;
72             my $serialized = Storable::freeze( \ (my $anon_scalar) );
73              
74             my %frozen_attr; #to be constructed
75             my @package_list = ref $self;
76             my %package_seen = ( ref($self) => 1 ); #ignore diamond/looped base classes :-)
77             PACKAGE:
78             while( my $package = shift @package_list) {
79             #make sure we add any base classes to the list of
80             #packages to examine for attributes.
81             { no strict 'refs';
82             for my $base_class ( @{"${package}::ISA"} ) {
83             push @package_list, $base_class
84             if !$package_seen{$base_class}++;
85             }
86             }
87             #examine attributes from known packages only
88             my $attr_list_ref = $attributes_of{$package} or next PACKAGE;
89              
90             #look for any attributes of this object for this package
91             ATTR:
92             for my $attr_ref ( @{$attr_list_ref} ) {
93             #nothing to do if attr not set for this object
94             next ATTR if !exists $attr_ref->{ref}{$id};
95             #save the attr by name into the package hash
96             $frozen_attr{$package}{ $attr_ref->{name} }
97             = $attr_ref->{ref}{$id};
98             }
99             }
100              
101             $self->STORABLE_freeze_post($cloning, \%frozen_attr)
102             if UNIVERSAL::can($self, "STORABLE_freeze_post");
103             return ($serialized, \%frozen_attr );
104             }
105              
106             sub STORABLE_thaw {
107             #croak "must be called from Storable" unless caller eq 'Storable';
108             #unfortunately, Storable never appears on the call stack.
109             my($self, $cloning, $serialized, $frozen_attr_ref) = @_;
110             #we can ignore $serialized, as we know it's an anon_scalar.
111             $self->STORABLE_thaw_pre($cloning, $frozen_attr_ref)
112             if UNIVERSAL::can($self, "STORABLE_thaw_pre");
113             my $id = ident($self);
114             PACKAGE:
115             while( my ($package, $pkg_attr_ref) = each %$frozen_attr_ref ) {
116             croak "unknown base class '$package' seen while thawing ".ref($self)
117             if ! UNIVERSAL::isa($self, $package);
118             my $attr_list_ref = $attributes_of{$package};
119             ATTR:
120             for my $attr_ref ( @{$attr_list_ref} ) { #for known attrs...
121             #nothing to do if frozen attr doesn't exist
122             next ATTR if !exists $pkg_attr_ref->{ $attr_ref->{name} };
123             #block attempts to meddle with existing objects
124             croak "trying to modify existing attributes for $package"
125             if exists $attr_ref->{ref}{$id};
126             #ok, set the attribute
127             $attr_ref->{ref}{$id}
128             = delete $pkg_attr_ref->{ $attr_ref->{name} };
129             }
130             if( my @extra_keys = keys %$pkg_attr_ref ) {
131             #this is probably serious enough to throw an exception.
132             #however, TODO: it would be nice if the class could somehow
133             #indicate to ignore this problem.
134             croak "unknown attribute(s) seen while thawing"
135             ." class $package: " . join(q{, }, @extra_keys);
136             }
137             }
138             $self->STORABLE_thaw_post($cloning)
139             if UNIVERSAL::can($self, "STORABLE_thaw_post");
140             }
141              
142             1; # Magic true value required at end of module
143             __END__