File Coverage

blib/lib/Class/LazyObject.pm
Criterion Covered Total %
statement 90 91 98.9
branch 15 18 83.3
condition 10 18 55.5
subroutine 19 19 100.0
pod n/a
total 134 146 91.7


line stmt bran cond sub pod time code
1             package Class::LazyObject;
2 6     6   461996 use strict;
  6         17  
  6         258  
3 6     6   32 use warnings;
  6         11  
  6         178  
4              
5 6     6   31 use Carp qw();
  6         16  
  6         149  
6              
7             BEGIN {
8 6     6   33 use vars '$VERSION';
  6         11  
  6         390  
9 6     6   136 $VERSION = '0.10';
10             }
11              
12 6     6   32 use vars '$AUTOLOAD';
  6         13  
  6         409  
13              
14             #We want to inflate calls to methods defined in UNIVERSAL, but we implicitly inherit from UNIVERSAL.
15             #As long as we predeclare the methods here, they will override those in UNIVERSAL, but since they are never defined, AUTOLOAD will be called:
16 6     6   21154 use subs grep {defined UNIVERSAL::can(__PACKAGE__, $_)} keys %UNIVERSAL::;
  6         219  
  6         29  
  24         392  
17              
18             sub AUTOLOAD
19             {
20 96     96   58094 my $self = $_[0]; #don't shift it, since we will need to access this directly later.
21              
22 96         533 $AUTOLOAD =~ /.*::(\w+)/;
23 96         192 my $method = $1;
24            
25 96   66     529 my $class_method = (ref($self) || $self).'::Methods';#call all class methods on this.
26            
27 96 100 66     719 if (($method eq 'new') && !ref($self))
28             {
29             #new was called on a class, rather than an object, so we should actually construct ourselves, rather than passing this to whatever we're proxying.
30 40         176 return $class_method->new(@_);
31             }
32            
33 56 100       494 ref($self) or return $class_method->$method(@_[ 1 .. $#_ ]); #If this was called as a class method, pass it on to ::Methods but don't pass OUR package name.
34            
35 50 50       195 print "Lazy...\n" if $class_method->get_classdata('debug');
36            
37 50         8336 my $object; #this is the object we will eventually replace ourselves with.
38            
39 50 100 66     215 if ( ref($$self) && UNIVERSAL::isa($$self, $class_method->get_classdata('class')) )
40             {
41 10         180 $object = $$self;
42             }
43             else
44             {
45 40         116 $object = $class_method->get_classdata('inflate')->($class_method->get_classdata('class'), $$self);
46 40         679 $$self = $object; #don't create this again.
47             }
48            
49 50         77 $_[0] = $object; #replace ourselves with the object.
50            
51             goto (
52 50   66     2242 UNIVERSAL::can($_[0], $method) ||
53             $class_method->_prepareAUTOLOADRef($_[0], ref($_[0]).'::'.$method) || #UNIVERSAL::can can't detect if a method is AUTOLOADed, so we have to.
54             Carp::croak(sprintf qq{Can\'t locate object method "%s" via package "%s" }, $method, ref $_[0] )#Error message stolen from Class::WhiteHole
55             );
56             }
57              
58             sub DESTROY #You won't AUTOLOAD this! Muahahaha!
59             {
60 40     40   4704 undef; #This is here because certain perl versions can't handle completely emtpy subs.
61             }
62              
63              
64             #class method to see whether something is lazy?
65             #class methods for original isa and can
66              
67             #---------
68             package Class::LazyObject::Methods;
69             #stick all of our class methods here so we don't pollute Class::LazyObject's namespace.
70             #everything in this class should be called as class methods, NOT object methods.
71              
72 6     6   13877 use Carp::Clan '^Class::LazyObject(::|$)';
  6         59860  
  6         52  
73 6     6   21381 use Class::Data::TIN qw(get_classdata);
  6         135900  
  6         586  
74 6     6   52348 use Class::ISA;
  6         173194  
  6         4836  
75              
76             sub _findAUTOLOADPackage
77             {
78             #Takes 1 argument, either an object or the name of a package.
79             #Returns the name of the package containing the sub AUTOLOAD that would be called when $first_arg->AUTOLOAD was called
80             #In other words, it traverses the inheritance hierarchy the same way Perl does until it finds an AUTOLOAD, and returns the name of the package containing the AUTOLOAD.
81             #Returns undef if AUTOLOAD is not in the inheritance hierarchy.
82            
83 16     16   20 shift;#Don't care about our package name.
84            
85 16         22 my $object_or_package = shift;
86 16   33     49 my $orig_class = ref($object_or_package) || $object_or_package;
87            
88 16 50       70 return undef unless UNIVERSAL::can($orig_class, 'AUTOLOAD');
89            
90 16         62 my @classes = (Class::ISA::self_and_super_path($orig_class), 'UNIVERSAL');
91            
92 16         411 my $package;
93 16         227 foreach my $class (@classes)
94             {
95 6     6   66 no strict 'refs';#Symbol table munging ahead
  6         12  
  6         798  
96 24         29 $package = $class;
97 24 100       23 last if defined(*{$package.'::AUTOLOAD';}{CODE});
  24         93  
98             }
99            
100 16         40 return $package;
101             }
102              
103             sub _prepareAUTOLOADRef
104             {
105             #Takes 2 arguments:
106             # either an object or the name of a package
107             # the fully qualified method name to make AUTOLOAD think it was called as a result of
108             #Sets the appropriate package's $AUTOLOAD so that when the AUTOLOAD method is called on the first argument, it will think it was called as a result of a call to the method specified by the second argument.
109             #Returns the result of (UNIVERSAL::can($first_arg, 'AUTOLOAD'));
110            
111 19     19   31 my $class = shift;
112              
113 19         38 my ($object, $method) = @_;
114            
115 19 100       79 if (UNIVERSAL::can($object, 'AUTOLOAD'))#no point in doing any of this if it can't AUTOLOAD.
116             {
117 16         56 my $package = $class->_findAUTOLOADPackage($object);
118            
119             {
120 6     6   35 no strict 'refs';
  6         10  
  6         1743  
  16         24  
121 16         20 *{$package.'::AUTOLOAD'} = \$method;
  16         48  
122             }
123             }
124            
125 19         723 return UNIVERSAL::can($object, 'AUTOLOAD');
126             }
127              
128             #defaults, these are overridable when someone calls ->inherit
129             Class::Data::TIN->new(__PACKAGE__,
130             inflate => sub {return $_[0]->new_inflate($_[1]);},
131             debug => 0,
132             );
133              
134             sub inherit
135             {
136             #calls to Class::LazyObject->inherit are forwarded here.
137            
138 6     6   2512 my $class = shift; #don't care about our own package name.
139 6         41 my %params = @_;
140            
141 6         193 my @required_params = qw(inflated_class deflated_class);
142            
143 6         18 foreach my $param (@required_params)
144             {
145 11 100       55 croak "You did not pass '$param', which is a required parameter." unless exists $params{$param};
146             }
147            
148 5         18 my %param_map = ( #keys are key names in the parameters passed to this function. Values are corrisponding class data names.
149             inflated_class => 'class'
150             );
151            
152 5         21 my %class_data = %params;
153 5         20 delete @class_data{keys %param_map, 'deflated_class'};#we'll stick these in with their appropriate names:
154 5         42 @class_data{values %param_map} = @params{keys %param_map};#pass the parameters whose names have changed
155            
156            
157 5         18 my $method_package = $params{deflated_class}.'::Methods';
158            
159             {
160 6     6   40 no strict 'refs'; #more symbol table munging
  6         13  
  6         1308  
  5         9  
161 5         10 push(@{$method_package.'::ISA'}, __PACKAGE__); #Create a package to hold all the methods, that inherits from THIS class, or add this class to its inheritance if it does exist. #Should this be $class instead of __PACKAGE__
  5         83  
162             #^Push is used instead of unshift so that someone can override their ::Methods package with its own inheritence hierarchy, and methods will be called here only AFTER Perl finds they don't exist in the overridden ISA.
163             }
164            
165 5         39 Class::Data::TIN->new($method_package, %class_data);
166             }
167              
168             sub new
169             {
170 40     40   78 my ($own_package, $class, $id) = @_;#first argument is this method's, class, not the lazy object's
171            
172 40 50 33     112 if (ref($id) && UNIVERSAL::isa($id, $own_package->get_classdata('class')))
173             {
174 0         0 croak "A Lazy Object's ID cannot be a an object of same class (or of a class inherited from the one) it is proxying!";
175             }
176              
177 40         195 return bless \$id, $class;
178             }
179              
180             1;
181              
182             #LAUNDRY LIST:
183             #LAZYNESS, impatience, hubris
184             #should we document the $AUTOLOAD persistence thingy as a caveat?
185             #CALLING AUTOLOAD on inflate
186             #CAVEAT: can't distinguish between no id and an id of undef.
187             # -solve by storing a Class::LazyObject::NoID object instead of undef?
188             #Does goto propogate scalar/list context?
189             #Lvalue subs?
190             #PROBLEM: Can't inherit from a lazy object (that has already called inherit) it right now without calling inherit because the corrisponding ::Methods class hasn't been created yet. Instead of being completely nonpolluting, can we create a method with a long convoluted name like __________Class_____LazyObject______Find____Methods and use the NEXT module to make redispatch any calls of it that are object method calls instead of static class calls?
191             #Does ORL's can work correctly for classes that inherit from an unrealized class?
192              
193              
194             __END__