line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Class::Mutator - Run-time Dynamic Multiple Inheritance |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
package Frog; |
10
|
|
|
|
|
|
|
use Class::Mutator qw( -isasubclass ); |
11
|
|
|
|
|
|
|
sub new { ... } |
12
|
|
|
|
|
|
|
sub eat_flies { ... } |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
package Prince; |
15
|
|
|
|
|
|
|
sub be_charming { ... } |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
my $froggie = Frog->new; |
18
|
|
|
|
|
|
|
$froggie->mutate('Prince'); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Now the froggie has been reblessed into a "Frog Prince" |
21
|
|
|
|
|
|
|
# class and can take advantage of methods in both classes. |
22
|
|
|
|
|
|
|
$froggie->eat_flies; |
23
|
|
|
|
|
|
|
$froggie->be_charming; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 DESCRIPTION |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
Class::Mutator adds the power of "dynamic polymorphism" to Perl |
28
|
|
|
|
|
|
|
objects. |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
Any object that inherits Class::Mutator principally gains two new |
31
|
|
|
|
|
|
|
methods, mutate and unmutate that allows them to add methods to |
32
|
|
|
|
|
|
|
themselves at runtime from other packages. The most recently mutated |
33
|
|
|
|
|
|
|
packages take precedence when methods with the same name are defined |
34
|
|
|
|
|
|
|
in more than one package. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
This module is similar to Sex.pm, which Michael Schwern was working |
37
|
|
|
|
|
|
|
on around the same time, only a little bit more predictable. It |
38
|
|
|
|
|
|
|
came about while I was doing some training at the BBC and someone |
39
|
|
|
|
|
|
|
asked how you could do this easily; after discussion with my fellow |
40
|
|
|
|
|
|
|
London.pm'ers, in particular Piers Cawley, this module came about. |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
More recently Matthew Simon Cavalletto sent me a version with |
43
|
|
|
|
|
|
|
everything I had been meaning to do on the module a little after I |
44
|
|
|
|
|
|
|
uploaded version 0.03 which only had more substantial tests. So major |
45
|
|
|
|
|
|
|
kudos to Matthew. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head1 USE |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
To enable a class of objects to mutate, make it a subclass of |
50
|
|
|
|
|
|
|
Class::Mutator. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
package MyBaseClass; |
53
|
|
|
|
|
|
|
use Class::Mutator; |
54
|
|
|
|
|
|
|
push @ISA, 'Class::Mutator'; |
55
|
|
|
|
|
|
|
... |
56
|
|
|
|
|
|
|
MyBaseClass->new()->mutate( ... ); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
As a shortcut, you may pass the C<-isasubclass> flag in your use |
59
|
|
|
|
|
|
|
statement, which will produce the same result. |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
package MyBaseClass; |
62
|
|
|
|
|
|
|
use Class::Mutator '-isasubclass'; |
63
|
|
|
|
|
|
|
... |
64
|
|
|
|
|
|
|
MyBaseClass->new()->mutate( ... ); |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Finally, if you need to retroactively add mutation capabilities to |
67
|
|
|
|
|
|
|
an existing class, you can do so using the same syntax, with the |
68
|
|
|
|
|
|
|
target class passeds as a parameter. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
package main; |
71
|
|
|
|
|
|
|
use MyBaseClass; |
72
|
|
|
|
|
|
|
use Class::Mutator '-isasubclass' => 'MyBaseClass'; |
73
|
|
|
|
|
|
|
MyBaseClass->new()->mutate( ... ); |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
You can also import the specific methods and functions described |
76
|
|
|
|
|
|
|
below and call them directly. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
package main; |
79
|
|
|
|
|
|
|
use MyBaseClass; |
80
|
|
|
|
|
|
|
use Class::Mutator 'mutate'; |
81
|
|
|
|
|
|
|
mutate( MyBaseClass->new(), ... ); |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=cut |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
package Class::Mutator; |
86
|
|
|
|
|
|
|
$VERSION='0.04'; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
@EXPORT_OK = qw( mutate unmutate apply_mutation modify_mutation_list |
89
|
|
|
|
|
|
|
get_mutation_list build_mutation_package ); |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub import { |
92
|
2
|
|
|
2
|
|
4393
|
my $class = shift; |
93
|
|
|
|
|
|
|
|
94
|
2
|
100
|
|
|
|
12
|
if ( ! scalar @_ ) { |
|
|
50
|
|
|
|
|
|
95
|
1
|
|
|
|
|
16
|
return; |
96
|
|
|
|
|
|
|
} elsif ( $_[0] eq '-isasubclass' ) { |
97
|
1
|
|
33
|
|
|
10
|
my $target_class = $_[1] || ( caller )[0]; |
98
|
1
|
|
|
1
|
|
29447
|
no strict 'refs'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
188
|
|
99
|
1
|
|
|
|
|
2184
|
push @{"$target_class\::ISA"}, $class |
|
0
|
|
|
|
|
0
|
|
100
|
1
|
50
|
|
|
|
3
|
unless ( grep { $_ eq $class } @{"$target_class\::ISA"} ); |
|
1
|
|
|
|
|
8
|
|
101
|
|
|
|
|
|
|
} else { |
102
|
0
|
0
|
|
|
|
0
|
require Exporter and goto &Exporter::import # lazy Exporter |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
1
|
|
|
1
|
|
9
|
use strict; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
595
|
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head1 METHODS |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
These methods provide the module's public object-oriented interface. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head2 mutate |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
$object->mutate( @packages ); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Adds a mutation. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=cut |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
sub mutate { |
121
|
3
|
|
|
3
|
1
|
1567
|
my $self = shift; |
122
|
3
|
|
|
|
|
6
|
my @packages = @_; |
123
|
3
|
|
|
|
|
10
|
apply_mutation($self, '+', @packages); |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head2 unmutate |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
$object->unmutate( @packages ); |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Remove mutation abilities via a package |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=cut |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub unmutate { |
135
|
3
|
|
|
3
|
1
|
828
|
my $self = shift; |
136
|
3
|
|
|
|
|
8
|
my @packages = @_; |
137
|
3
|
|
|
|
|
7
|
apply_mutation($self, '-', @packages); |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head1 FUNCTIONS |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
These functions are used internally to support the methods described |
144
|
|
|
|
|
|
|
above. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head2 apply_mutation |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
$reblessed_object = apply_mutation( $object, $op, @packages ); |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
Builds a new package list, based on the current package for a given |
151
|
|
|
|
|
|
|
object, the operator (either "+" or "-"), and the packages to be |
152
|
|
|
|
|
|
|
added or removed. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=cut |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub apply_mutation { |
157
|
6
|
|
|
6
|
1
|
10
|
my $self = shift; |
158
|
6
|
|
|
|
|
19
|
bless $self, build_mutation_package( modify_mutation_list( $self, @_ ) ) |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head2 get_mutation_list |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
@packages = get_mutation_list($object_or_class); |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Returns the list of classes a mutated object is based on. If the |
166
|
|
|
|
|
|
|
object is not a mutant, its normal class name will be returned. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=cut |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub get_mutation_list { |
171
|
6
|
|
|
6
|
1
|
8
|
my $self = shift; |
172
|
|
|
|
|
|
|
|
173
|
6
|
|
33
|
|
|
16
|
my $curr = ref($self) || $self; |
174
|
|
|
|
|
|
|
|
175
|
6
|
100
|
|
|
|
29
|
if ($curr =~ s/^Class::Mutator:://) { |
176
|
5
|
|
|
|
|
15
|
return map { s/__/::/g; $_ } split /::/,$curr; |
|
11
|
|
|
|
|
17
|
|
|
11
|
|
|
|
|
30
|
|
177
|
|
|
|
|
|
|
} else { |
178
|
1
|
|
|
|
|
3
|
return $curr |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=head2 build_mutation_package |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
$new_class_name = build_mutation_package(@packages); |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Builds the new mutation package. Returns the name of the new class. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=cut |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub build_mutation_package { |
191
|
6
|
|
|
6
|
1
|
12
|
my @class_list = @_; |
192
|
6
|
|
|
|
|
11
|
my @ingredients = map { s/::/__/g; $_ } @class_list; |
|
12
|
|
|
|
|
16
|
|
|
12
|
|
|
|
|
41
|
|
193
|
6
|
|
|
|
|
16
|
my $package_name = 'Class::Mutator::'.join('::',@ingredients); |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# If our target class has an inheritance tree, we've already |
196
|
|
|
|
|
|
|
# set it up on a previous invocation, so there's nothing to do. |
197
|
1
|
|
|
1
|
|
9
|
no strict 'refs'; |
|
1
|
|
|
|
|
17
|
|
|
1
|
|
|
|
|
445
|
|
198
|
6
|
100
|
|
|
|
8
|
unless ( scalar @{$package_name . '::ISA'} ) { |
|
6
|
|
|
|
|
72
|
|
199
|
5
|
|
|
|
|
10
|
@{$package_name . '::ISA'} = reverse @class_list; |
|
5
|
|
|
|
|
97
|
|
200
|
|
|
|
|
|
|
} |
201
|
6
|
|
|
|
|
42
|
return $package_name; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head2 modify_mutation_list |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
@packages = modify_mutation_list($object_or_class, $op, @packages); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
Builds a new package list based on the current packages list and the |
209
|
|
|
|
|
|
|
operation and package (the operation is upon) handed to this method. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=cut |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub modify_mutation_list { |
214
|
6
|
|
|
6
|
1
|
7
|
my $self = shift; |
215
|
6
|
|
|
|
|
13
|
my ($op, @packages) = @_; |
216
|
|
|
|
|
|
|
|
217
|
6
|
|
|
|
|
13
|
my @active_classes = get_mutation_list( $self ); |
218
|
|
|
|
|
|
|
|
219
|
6
|
|
|
|
|
13
|
foreach my $package ( @packages ) { |
220
|
8
|
|
|
|
|
13
|
@active_classes = grep { $_ ne $package } @active_classes; |
|
15
|
|
|
|
|
48
|
|
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
6
|
100
|
|
|
|
17
|
if ($op eq '+') { |
|
|
50
|
|
|
|
|
|
224
|
3
|
|
|
|
|
6
|
push(@active_classes, @packages); |
225
|
|
|
|
|
|
|
} elsif ($op eq '-') { |
226
|
|
|
|
|
|
|
# We've already got this functionality out of the grep above |
227
|
|
|
|
|
|
|
} else { |
228
|
|
|
|
|
|
|
# Invalid operation |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
6
|
|
|
|
|
22
|
return @active_classes; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head1 AUTHORS |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Greg McCarroll |
237
|
|
|
|
|
|
|
Mail : greg@mccarroll.demon.co.uk |
238
|
|
|
|
|
|
|
Jabber : greg@jabber.mccarroll.org.uk |
239
|
|
|
|
|
|
|
Homepage : http://www.mccarroll.org.uk/~gem/ |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
Matthew Simon Cavalletto |
242
|
|
|
|
|
|
|
Mail : simonm@cavalletto.org, |
243
|
|
|
|
|
|
|
Homepage : http://www.evoscript.org/ |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=cut |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
1; |