line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Class::Proxy::Lite; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
65322
|
use strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
91
|
|
4
|
2
|
|
|
2
|
|
12
|
use vars qw($VERSION); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
148
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
$VERSION = '1.01'; |
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
12
|
use constant TOKEN => 0; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
163
|
|
9
|
2
|
|
|
2
|
|
11
|
use constant RESOLVER => 1; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
76
|
|
10
|
2
|
|
|
2
|
|
10
|
use constant CACHED => 2; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
803
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub AUTOLOAD { |
13
|
6
|
|
|
6
|
|
3887
|
my $self_or_class = shift(); |
14
|
|
|
|
|
|
|
|
15
|
6
|
|
|
|
|
42
|
(my $method = $Class::Proxy::Lite::AUTOLOAD) =~ s/(.*):://; |
16
|
|
|
|
|
|
|
|
17
|
6
|
|
|
|
|
15
|
my $is_class_method = ref($self_or_class) eq ''; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# --- Check for special cases and non-references |
20
|
6
|
50
|
|
|
|
22
|
return undef if $method eq 'DESTROY'; |
21
|
6
|
50
|
33
|
|
|
23
|
return undef if $method eq 'import' and $is_class_method; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# --- XXX Try to deal with can and isa? |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# --- Emulate class method new() |
26
|
6
|
100
|
|
|
|
18
|
if ($is_class_method) { |
27
|
2
|
50
|
33
|
|
|
26
|
die "Can't call a class method on Class::Proxy::Lite or a subclass" |
28
|
|
|
|
|
|
|
unless $method eq 'new' |
29
|
|
|
|
|
|
|
and UNIVERSAL::isa($self_or_class, __PACKAGE__); |
30
|
|
|
|
|
|
|
# --- Create a proxy |
31
|
2
|
|
|
|
|
7
|
my ($token, $resolver, $cached) = @_; |
32
|
2
|
|
|
|
|
4
|
my @self; |
33
|
2
|
|
|
|
|
5
|
$self[TOKEN] = $token; |
34
|
2
|
|
|
|
|
5
|
$self[RESOLVER] = $resolver; |
35
|
2
|
50
|
|
|
|
7
|
$self[CACHED] = $cached if defined $cached; |
36
|
2
|
|
|
|
|
12
|
return bless \@self, $self_or_class; |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# --- Reject attempts to call functions in Class::Proxy::Lite |
40
|
4
|
50
|
33
|
|
|
19
|
die "No such function: $Class::Proxy::Lite::AUTOLOAD" |
41
|
|
|
|
|
|
|
if $is_class_method |
42
|
|
|
|
|
|
|
and $self_or_class ne 'Class::Proxy::Lite'; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# --- Resolve the token |
45
|
4
|
|
|
|
|
24
|
my ($token, $resolver, $cached) = @$self_or_class[TOKEN, RESOLVER, CACHED]; |
46
|
4
|
|
|
|
|
8
|
my $target; |
47
|
4
|
50
|
|
|
|
13
|
if ($cached) { |
48
|
0
|
|
0
|
|
|
0
|
$target = $$cached ||= $resolver->($token); |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
else { |
51
|
4
|
|
|
|
|
13
|
$target = $resolver->($token); |
52
|
|
|
|
|
|
|
} |
53
|
4
|
50
|
33
|
|
|
41
|
die "Couldn't resolve proxy target" |
54
|
|
|
|
|
|
|
unless defined $target and ref $target; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# --- These don't work -- see under `Default UNIVERSAL methods' |
57
|
|
|
|
|
|
|
# in perlobj for an explanation |
58
|
|
|
|
|
|
|
# return ref($target)->isa(shift) if $method eq 'isa'; |
59
|
|
|
|
|
|
|
# return ref($target)->can(shift) if $method eq 'can'; |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# --- Invoke the method of the same name on the target object |
62
|
|
|
|
|
|
|
# goto &{UNIVERSAL::can($target, $method)} won't work, |
63
|
|
|
|
|
|
|
# because $target might rely upon its own AUTOLOAD!! |
64
|
2
|
|
|
2
|
|
11
|
no strict 'refs'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
327
|
|
65
|
|
|
|
|
|
|
return wantarray |
66
|
4
|
50
|
|
|
|
24
|
? @{ [ $target->${method}(@_) ] } |
|
0
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
: $target->${method}(@_) |
68
|
|
|
|
|
|
|
; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
1; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head1 NAME |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Class::Proxy::Lite - Simple, lightweight object proxies |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head1 SYNOPSIS |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# Make a proxy to a particular object |
82
|
|
|
|
|
|
|
$proxy = Class::Proxy::Lite->new($token, \&resolver); |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Make a caching proxy |
85
|
|
|
|
|
|
|
$proxy = Class::Proxy::Lite->new($token, \&resolver, \$cache); |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Methods invoked on the proxy are passed to the target object |
88
|
|
|
|
|
|
|
$proxy->foo(...); |
89
|
|
|
|
|
|
|
$proxy->bar(...); |
90
|
|
|
|
|
|
|
$proxy->etc(...); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head1 DESCRIPTION |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Each instance of this class serves as a proxy to a target object. The proxy |
95
|
|
|
|
|
|
|
is constructed from a I and a I. The resolver is a code |
96
|
|
|
|
|
|
|
reference called with the token as its only argument; its job is to resolve |
97
|
|
|
|
|
|
|
the token into a reference to the desired target object. |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
The proxy doesn't hold a reference to its target; instead, the token must be |
100
|
|
|
|
|
|
|
resolved each time a method call is made on the proxy. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head1 METHODS |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 new |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
$proxy = Class::Proxy::Lite->new($token, \&resolver); |
107
|
|
|
|
|
|
|
$proxy = Class::Proxy::Lite->new($token, \&resolver, \$cache); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
Construct a proxy. The resolver is expected to return an object exactly |
110
|
|
|
|
|
|
|
equivalent (if not identical) to the desired target object. This constraint |
111
|
|
|
|
|
|
|
can't be formally enforced by this module, so your resolver must be written |
112
|
|
|
|
|
|
|
in such a way as to meet the constraint itself. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
If you want one-time resolution, you may pass a reference to an undefined scalar |
115
|
|
|
|
|
|
|
variable as a third argument to the C method; this will be used to cache |
116
|
|
|
|
|
|
|
the target object the first time it's resolved, and as a result the target |
117
|
|
|
|
|
|
|
object won't need to be resolved again. Or you might pass a reference to a tied |
118
|
|
|
|
|
|
|
variable that implements caching with some sort of expiry. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
(There's a lot of room for clever hacks here. For instance, you could use a |
121
|
|
|
|
|
|
|
resolver that returns a different object each time it's called. Also, |
122
|
|
|
|
|
|
|
consider passing a closure as the resolver rather than a plain old reference |
123
|
|
|
|
|
|
|
to a function.) |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
B Strictly speaking, the method C doesn't exist as such: it isn't |
126
|
|
|
|
|
|
|
actually defined. Instead, it's emulated using C (see below) -- |
127
|
|
|
|
|
|
|
B<< but only when called as a class method! >> This way, your target |
128
|
|
|
|
|
|
|
objects' class(es) can safely implement a method C that can be called |
129
|
|
|
|
|
|
|
as either a class method or an object method: |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$obj1 = MyClass->new(...); |
132
|
|
|
|
|
|
|
$obj2 = $obj1->new(...); |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
See L for information on how to implement this style of |
135
|
|
|
|
|
|
|
constructor. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
When C is called as a class method on your own class, |
138
|
|
|
|
|
|
|
L isn't involved (unless you set up |
139
|
|
|
|
|
|
|
your objects' classes to inherit from it, which is a very bad idea). When |
140
|
|
|
|
|
|
|
C is called as an object method, the call is passed on to the target |
141
|
|
|
|
|
|
|
object just as would happen for any other object method call. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=head2 AUTOLOAD |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
This is where the action takes place. It simply calls the resolver to get a |
146
|
|
|
|
|
|
|
reference to the target object, then passes the method call on to it. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
The methods C and C are special-cased; the former is |
149
|
|
|
|
|
|
|
ignored, while the latter is ignored if and only if it was invoked on an |
150
|
|
|
|
|
|
|
object (i.e., not called implicitly as the result of a C |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
Except for C and C, all methods invoked on this class or a |
153
|
|
|
|
|
|
|
subclass of it (as opposed to methods invoked on an actual object) result in |
154
|
|
|
|
|
|
|
an exception being thrown. An exception is also thrown if the resolver |
155
|
|
|
|
|
|
|
returns C or a non-reference -- in other words, if it can't resolve |
156
|
|
|
|
|
|
|
the token into an actual object. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
B Never call AUTOLOAD directly! |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head1 SUBCLASSING |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Depending on your needs, it may not be necessary to subclass |
163
|
|
|
|
|
|
|
L. If you do, however, your subclass |
164
|
|
|
|
|
|
|
will probably look something like this: |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
package MyObject::Proxy; |
167
|
|
|
|
|
|
|
@ISA = qw(Class::Proxy::Lite); |
168
|
|
|
|
|
|
|
sub new { |
169
|
|
|
|
|
|
|
my ($cls, $target) = @_; |
170
|
|
|
|
|
|
|
my $token = obj2token($target); |
171
|
|
|
|
|
|
|
my $resolver = \&token2obj; |
172
|
|
|
|
|
|
|
return $self->SUPER::new($token, $resolver); |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
sub obj2token { ... } |
175
|
|
|
|
|
|
|
sub token2obj { ... } |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
See F for a slightly different example. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
C was designed to avoid method name clashes; the only |
180
|
|
|
|
|
|
|
method defined for it is C. If your subclass must inherit from |
181
|
|
|
|
|
|
|
another class that uses AUTOLOAD, this is probably not the right solution |
182
|
|
|
|
|
|
|
for you. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head1 BACKGROUND |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
L didn't fit my needs. I was implementing an |
187
|
|
|
|
|
|
|
object model in which objects are loaded dynamically and references to |
188
|
|
|
|
|
|
|
loaded objects are stored in a master table. I wanted a solution that |
189
|
|
|
|
|
|
|
served both as a proxy and a reference (generally speaking) to an object. |
190
|
|
|
|
|
|
|
This module is what resulted. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head1 LIMITATIONS |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Apparently, it's not possible to catch calls to the C and C |
195
|
|
|
|
|
|
|
methods on B of C. This makes it impossible |
196
|
|
|
|
|
|
|
to implement a true proxy without defining C and |
197
|
|
|
|
|
|
|
C, which I'm reluctant to do. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
The following note in L (under `Default UNIVERSAL methods') |
200
|
|
|
|
|
|
|
appears to explain the problem: |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
NOTE: `can' directly uses Perl's internal code for method |
203
|
|
|
|
|
|
|
lookup, and `isa' uses a very similar method and cache-ing |
204
|
|
|
|
|
|
|
strategy. This may cause strange effects if the Perl code |
205
|
|
|
|
|
|
|
dynamically changes @ISA in any package. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
I might be wrong about all this, though; any insights on this problem are |
208
|
|
|
|
|
|
|
welcome. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head1 SEE ALSO |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
L is a better alternative for more sophisticated |
213
|
|
|
|
|
|
|
proxy capabilities. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=head1 VERSION |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
1.01 |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 AUTHOR |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
Paul Hoffman . |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head1 CREDITS |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Thanks to Kurt Starsinic (KSTAR) for L, |
226
|
|
|
|
|
|
|
which got me thinking, and to Murat Uenalan (MUENALAN) for |
227
|
|
|
|
|
|
|
L, which set a good example. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head1 COPYRIGHT |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Copyright 2003 Paul M. Hoffman. All rights reserved. |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
This program is free software; you can redistribute it and modify it under |
234
|
|
|
|
|
|
|
the same terms as Perl itself. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=cut |
237
|
|
|
|
|
|
|
|