line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Class::Tag;
|
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
#use 5.006;
|
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
24145
|
use strict qw[vars subs];
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
206
|
|
6
|
|
|
|
|
|
|
$Class::Tag::VERSION = '0.10';
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 NAME
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
Class::Tag - programmatically label (mark) classes, methods, roles and modules with meta-data tags (key/value pairs) and query those tags
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 Warning
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Any specific interface that Class::Tag exposes may change (as it already did) until version 1.0 is reached.
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
{
|
19
|
|
|
|
|
|
|
package Foo;
|
20
|
|
|
|
|
|
|
use Class::Tag 'tagged'; # tagging Foo class with 'tagged' tag
|
21
|
|
|
|
|
|
|
}
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# query 'tagged' tag on the Foo and Bar...
|
24
|
|
|
|
|
|
|
Class::Tag->tagged('Foo'); # true
|
25
|
|
|
|
|
|
|
Class::Tag->tagged('Bar'); # false
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
{
|
28
|
|
|
|
|
|
|
package Bar;
|
29
|
|
|
|
|
|
|
use Class::Tag { class => 'is cool', author => 'metadoo' };
|
30
|
|
|
|
|
|
|
}
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
Class::Tag->class( 'Bar') eq 'is cool'; # true
|
33
|
|
|
|
|
|
|
Class::Tag->author('Bar') eq 'metadoo'; # true
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
See DESCRIPTION for more options.
|
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
Sometimes it is necessary to programmatically tag modules and classes with some meta-data tags (arbitrary labels or key/value pairs) to be able to assert that you deal with proper classes (modules), methods and roles. Such need typically arises for plug-in modules, application component modules, complex class inheritance hierarchies, etc.
|
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Class::Tag allows programmatically label (mark) classes and modules with arbitrary inheritable tags (key/value pairs) without collision with methods/attributes/functions of the class/module and query those tags on arbitrary classes and modules.
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
The syntax of Class::Tag usage is an interaction of B, B (class) and B (class): tagger applies tag to a target class. Names of tagger class (except Class::Tag itself) and tag can be chosen almost freely (with usual restrictions) to be read together as (subject and predicate in a) self-explanatory English sentence, with question semantics (e.g. in conditionals) optionally toggled by direct/indirect method call notation. The following synopsis illustrates.
|
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Directly using Class::Tag as tagger:
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
{
|
48
|
|
|
|
|
|
|
package Foo;
|
49
|
|
|
|
|
|
|
use Class::Tag 'tagged'; # tagging Foo class with 'tagged' tag
|
50
|
|
|
|
|
|
|
tag Class::Tag 'tagged'; # same, but at run-time
|
51
|
|
|
|
|
|
|
}
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# query 'tagged' tag on the Foo and Bar...
|
54
|
|
|
|
|
|
|
Class::Tag->tagged('Foo'); # true
|
55
|
|
|
|
|
|
|
Class::Tag->tagged('Bar'); # false
|
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
Tag can be removed completely from within the scope of the same package Foo:
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
{
|
60
|
|
|
|
|
|
|
package Foo;
|
61
|
|
|
|
|
|
|
# remove 'tagged' tag from Foo...
|
62
|
|
|
|
|
|
|
#no Class::Tag 'tagged'; # at compile-time, so will not work - instead...
|
63
|
|
|
|
|
|
|
untag Class::Tag 'tagged'; # at run-time
|
64
|
|
|
|
|
|
|
Class::Tag->tagged('Foo'); # false
|
65
|
|
|
|
|
|
|
}
|
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
However, since tagged() is now the read-write accessor for tag value, it may be easier to alter tag's value instead:
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
Class::Tag->tagged('Foo' => 0);
|
70
|
|
|
|
|
|
|
Class::Tag->tagged('Foo'); # false
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
If no tags are given, the 'is' tag is assumed:
|
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
package Foo;
|
75
|
|
|
|
|
|
|
use Class::Tag; # equivalent to...
|
76
|
|
|
|
|
|
|
use Class::Tag 'is'; # same
|
77
|
|
|
|
|
|
|
use Class::Tag (); # no tagging
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
New tagger class can be created by simply tagging package with special 'tagger_class' tag using either Class::Tag or any other tagger class, and then declaring specific tags to be used with that new tagger class. Declaration of specific tag is done by new tagger class applying this tag to itself. Declaring special 'AUTOLOAD' tag this way effectively declares that any tag can be used with new tagger class:
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
{
|
82
|
|
|
|
|
|
|
# this block can be used as "inline" tagger class definition
|
83
|
|
|
|
|
|
|
# or contents of this block can be loaded from Awesome.pm
|
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
package Awesome; # new tagger class
|
86
|
|
|
|
|
|
|
use Class::Tag 'tagger_class'; # must be before following declarations
|
87
|
|
|
|
|
|
|
use Awesome 'specific_tag'; # declares 'specific_tag' for use
|
88
|
|
|
|
|
|
|
use Awesome 'AUTOLOAD'; # declares that any tag can be used
|
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
1;
|
91
|
|
|
|
|
|
|
}
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Class::Tag->tagger_class('Awesome'); # true
|
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
Note that Awesome class is not required to be loaded from .pm file with use() or require(), it can be simply defined as above at any point in the code prior to using it as tagger class. Such tagger class definition is referred to as "inline" tagger class.
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
The Class::Tag itself is somewhat similar to the following implicit declaration:
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
package Class::Tag;
|
100
|
|
|
|
|
|
|
use Class::Tag 'tagger_class';
|
101
|
|
|
|
|
|
|
use Class::Tag 'AUTOLOAD';
|
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Attempt to use tag that has not been declared (assuming 'AUTOLOAD' declares any tag) raises exception. Values of declaration tags can be used to modify behavior of tags - see L"Declaration of tags"> section for details.
|
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
Any tagger class can be used as follows (in all following examples the original Class::Tag and Awesome tagger classes are interchangeable), assuming tags have been declared:
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Using default 'is' tag:
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
{
|
110
|
|
|
|
|
|
|
package Foo;
|
111
|
|
|
|
|
|
|
use Awesome;
|
112
|
|
|
|
|
|
|
use Awesome 'is'; # same
|
113
|
|
|
|
|
|
|
use Awesome { is => 1 }; # same
|
114
|
|
|
|
|
|
|
}
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
is Awesome 'Foo'; # true
|
117
|
|
|
|
|
|
|
is Awesome 'Bar'; # false
|
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Awesome->is('Foo'); # true
|
120
|
|
|
|
|
|
|
Awesome->is('Bar'); # false
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
$obj = bless {}, 'Foo';
|
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
is Awesome $obj; # true
|
125
|
|
|
|
|
|
|
Awesome->is($obj); # true
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
$obj = bless {}, 'Bar';
|
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
is Awesome $obj; # false
|
130
|
|
|
|
|
|
|
Awesome->is($obj); # false
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
Using tags 'class' and 'pureperl':
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
{
|
135
|
|
|
|
|
|
|
package Foo;
|
136
|
|
|
|
|
|
|
# tag class Foo with tags 'class' and 'pureperl' of Awesome tagger class...
|
137
|
|
|
|
|
|
|
use Awesome 'class';
|
138
|
|
|
|
|
|
|
use Awesome 'pureperl';
|
139
|
|
|
|
|
|
|
use Awesome 'class', 'pureperl'; # same
|
140
|
|
|
|
|
|
|
use Awesome { class => 1, pureperl => 1 }; # same
|
141
|
|
|
|
|
|
|
}
|
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Awesome->class( 'Foo'); # true
|
144
|
|
|
|
|
|
|
Awesome->pureperl('Foo'); # true
|
145
|
|
|
|
|
|
|
Awesome->class( 'Bar'); # false
|
146
|
|
|
|
|
|
|
Awesome->pureperl('Bar'); # false
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Using key/value pairs as tags (tag values) and using read-write tag accessors:
|
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
{
|
151
|
|
|
|
|
|
|
package Foo;
|
152
|
|
|
|
|
|
|
use Awesome { class => 'is cool', author => 'metadoo' };
|
153
|
|
|
|
|
|
|
}
|
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Awesome->author('Foo') eq 'metadoo' ; # true
|
156
|
|
|
|
|
|
|
Awesome->class( 'Foo') eq 'is cool' ; # true
|
157
|
|
|
|
|
|
|
Awesome->class( 'Foo' => 'pupe-perl') eq 'pupe-perl'; # true
|
158
|
|
|
|
|
|
|
Awesome->class( 'Foo') eq 'pupe-perl'; # true
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
$foo = bless {}, 'Foo';
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Awesome->class( $foo) eq 'is cool'; # true
|
163
|
|
|
|
|
|
|
Awesome->author($foo) eq 'metadoo'; # true (inheriting)
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Awesome->class( $foo) eq 'pupe-perl'; # true (inheriting)
|
166
|
|
|
|
|
|
|
Awesome->class( $foo => 'pupe-perl too') eq 'pupe-perl too'; # true (copy-on-write)
|
167
|
|
|
|
|
|
|
Awesome->class( $foo) eq 'pupe-perl too'; # true (copy-on-write)
|
168
|
|
|
|
|
|
|
Awesome->class( 'Foo') eq 'pupe-perl'; # true (unmodified)
|
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
In other words, tag values can be modified with samename accessors. Object instances from the class inherit tags from the class, so that modifying tag value on instance modifies that of a class and vice versa, except blessed-hash objects get their own, instance-specific values when modifying tag value on instance - copy-on-write approach.
|
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Inheriting tags, using for example the default 'is' tag:
|
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
{
|
175
|
|
|
|
|
|
|
package Foo;
|
176
|
|
|
|
|
|
|
use Awesome;
|
177
|
|
|
|
|
|
|
use Awesome 'is'; # same
|
178
|
|
|
|
|
|
|
}
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
@Bar::ISA = 'Foo';
|
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Awesome->is('Foo'); # true
|
183
|
|
|
|
|
|
|
Awesome->is('Bar'); # true ('is' tag inherited)
|
184
|
|
|
|
|
|
|
Awesome::is('Foo'); # true
|
185
|
|
|
|
|
|
|
Awesome::is('Bar'); # false (no tag inheritance)
|
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
By design, Class::Tag is a generalized framework for managing meta information (tags) about inheritable behaviors. Inheritable behaviors that can have meta-data tags attached include methods, classes, roles, etc. Tags are by necessity inheritable, as they need to be inherited together with behaviors they are supposed to describe.
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
Simple example of the meta-data tag is a class name, with tag's (boolean) value returned by isa(). Another simple example of meta-data tag is a method name, with its value returned by can(). Yet another meta-data tag example is a role name, with tag's value supposed to be returned by DOES(). But classes, methods and roles may also have other meta-data tags apart from their names. In particular, Class::Tag can easily be used to implement method attributes and even multiple "layers" of method attributes, for example:
|
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
package Zoo;
|
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub foo { 1 }
|
194
|
|
|
|
|
|
|
use Meta foo => { is => 'ro', returns => 'boolean' }; # 1-st "meta-layer"
|
195
|
|
|
|
|
|
|
use Meta2 foo => { author => 'metadoo', doc => 'is dead-simple' }; # 2-nd "meta-layer"
|
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
Such use opens possibilities for meta-programming and introspection. For example, method can access its own meta-data as follows:
|
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub foo { Meta->foo( ref($_[0])||$_[0] ) }
|
200
|
|
|
|
|
|
|
sub foo { Meta->foo( $_[0] ) } # nearly (but not exactly) same
|
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
Technically, Class::Tag is the constructor for special variety of class/object attributes that are orthogonal to (isolated from) conventional attributes/methods of the class. Being the same and being orthogonal at the same time is what required to be good carrier of meta information about inheritable behavior. And use of tagger classes is a way to extend and partition class's namespace into meaningful orthogonal domains, as well as to extend the notion of the meta-data tag in the domain-specific way.
|
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head1 Tagger classes
|
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Class::Tag itself serves as tagger class, and each tagger class is a "constructor" for other tagger classes, either loadable or inlined.
|
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
The use() of tagger class looks as if it exports chosen named tags into packages, but in fact it doesn't - tagger class itself provides samename accessor methods for those tags. As a result, tag names can be arbitrary without risk of collision, so that together with name of tagger class they can be selected to read somewhat meaningful (see examples in L"SYNOPSIS">) in the problem area domain that uses that specific tagger.
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head2 Tagger class construction
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
See L"SYNOPSIS"> for description of new tagger class creation. Tagger class can be created "inline", without using separate .pm file for it.
|
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
The value of 'tagger_class' tag is reserved for special use in the future, so it should not be used for anything to avoid incompatibility with future versions.
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=head2 Tagger class benefits
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
There are a few reasons to use multiple tagger classes in addition to or instead of Class::Tag itself:
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=over
|
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=item Name
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Name of the tagger class can be chosen to read naturally and meaningful, in either direct or indirect method call notations i.e. reversing order of tagger and tag names (doubling readability options), with semantically meaningful tags used in the context of given application or problem area domain.
|
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=item Restricted tagspace
|
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
The original Class::Tag tagger class allows to use any tag, except tag(), untag() and Perl's specials, like import(), can(), etc. are still reserved. In contrast, custom tagger classes may allow only specific tags to be used.
|
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item Isolated (orthogonal) tagspace
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Each tagger class has its own orthogonal tags namespace (tagspace), so that same tags of different tagger classes do not collide:
|
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
{
|
235
|
|
|
|
|
|
|
package Awesome;
|
236
|
|
|
|
|
|
|
use Class::Tag 'tagger_class';
|
237
|
|
|
|
|
|
|
use Awesome 'AUTOLOAD';
|
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
package Bad;
|
240
|
|
|
|
|
|
|
use Class::Tag 'tagger_class';
|
241
|
|
|
|
|
|
|
use Bad 'AUTOLOAD';
|
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
package Foo;
|
244
|
|
|
|
|
|
|
use Awesome 'really';
|
245
|
|
|
|
|
|
|
use Awesome { orthogonal => 'awesome' };
|
246
|
|
|
|
|
|
|
use Bad { orthogonal => 'bad' };
|
247
|
|
|
|
|
|
|
}
|
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
really Awesome 'Foo'; # true
|
250
|
|
|
|
|
|
|
really Bad 'Foo'; # false
|
251
|
|
|
|
|
|
|
Bad->orthogonal('Foo') eq 'bad'; # true
|
252
|
|
|
|
|
|
|
Awesome->orthogonal('Foo') eq 'awesome'; # true
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Without other tagger classes the tags namespace of Class::Tag would be exposed to higher risk of tags collision, since due to global nature of Perl classes there is always a possibility of collision when same tag is used for unrelated purposes (e.g. in the same inheritance chain, etc.).
|
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Since tagger class tags upon use() and classes usually do not export anything, it is often useful and possible to make some existing class a tagger to tag classes that use() it. Moreover, it can be done from a distance, without cognizance of the existing class. The same also applies to modules that are not classes.
|
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
However, making existing (non-empty) class/module a tagger class requires care to not collide with methods of that class - Class::Tag will raise an exception when such collision happens. It is better not to declare 'AUTOLOAD' for such tagger class.
|
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=item Meta-data domains
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
Tags of different tagger classes are intended to be dedicated to defining, managing and documenting different meta-data domains. It can be meta-data associated with specific module, application, problem, algorithm, etc. In particular, tagger class is an ideal place where to document its tags.
|
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=back
|
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=head2 Declaration of tags
|
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
Attempt to use tag that has not been declared (assuming 'AUTOLOAD' declares any tag) raises exception.
|
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
In addition, values of declaration tags can be used to modify behavior of tags and, thus, redefine/evolve the whole notion of the tag. If tag is declared with subroutine reference value, that subroutine is called when tag is accessed:
|
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
{
|
273
|
|
|
|
|
|
|
package Awesome; # new tagger class
|
274
|
|
|
|
|
|
|
use Class::Tag 'tagger_class'; # must be before following declarations
|
275
|
|
|
|
|
|
|
use Awesome specific_tag => \&accessor; # use \&accessor for 'specific_tag'
|
276
|
|
|
|
|
|
|
use Awesome AUTOLOAD => \&ACCESSOR; # use \&ACCESSOR for any tag
|
277
|
|
|
|
|
|
|
}
|
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Awesome->specific_tag( $class_or_obj, @args); # is equivalent to...
|
280
|
|
|
|
|
|
|
&accessor('Awesome', $class_or_obj, @args);
|
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
Awesome::specific_tag( $class_or_obj, @args); # is equivalent to...
|
283
|
|
|
|
|
|
|
&accessor( undef, $class_or_obj, @args);
|
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Awesome->any_other_tag($class_or_obj, @args); # is equivalent to...
|
286
|
|
|
|
|
|
|
&ACCESSOR('Awesome', $class_or_obj, @args);
|
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
Awesome::any_other_tag($class_or_obj, @args); # is equivalent to...
|
289
|
|
|
|
|
|
|
&ACCESSOR( undef, $class_or_obj, @args);
|
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
The Awesome class in above code may also be replaced with object of Awesome class. With custom accessors as above the entire tag syntax can be used for something different.
|
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=head1 Traditional alternatives
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
There are three natural alternative solutions: classes-as-tags, roles-as-tags and methods-as-tags. The classes-as-tags solution uses universal isa() method to see if class has specific parent, it effectively uses specific parent classes as tags. However, using parent classes just as tags is a limited solution since @ISA is used for different things and better be used for those things exclusively to avoid interferences.
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Using roles as tags do not involve modifying @ISA, but this approach relies on using single shared congested namespace, which means possibility of accidental collision, unless you specifically choose unnatural names (long, prefixed, capitalized, etc.) that are unlikely to collide or use unique names of existing modules as tags, which is an overkill in many cases.
|
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
Moreover, classes-as-tags and roles-as-tags solutions do not allow using values for tags (unless properly overridden).
|
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
Using methods-as-tags approach is about defining and using specific methods as tags. This approach is far better than classes-as-tags and roles-as-tags, but if specific method-tag need to be queried on unknown class/module, the following problems may arise:
|
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=over
|
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=item Name collision
|
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
It may be that class/module have defined samename method/attribute by coincidence. Possibility of collision is considerable for short readable names (like 'is'), especially for undocumented tags that are used internally and in case of subclassing. To avoid collision method-tags usually have some unique prefix and may be in upper-case and/or starting with '_', etc. The typical solution is using name of some module as unique suffix/prefix, and this is exactly what Class::Tag does in its own flexible way:
|
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
Foo->is_Awesome;
|
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Awesome->is('Foo');
|
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
Class::Tag allows to either dedicate specific tagger class, either loadable or inlined, just to serve as effective "prefix" with arbitrary risk-free tag names, or use some existing class/module as tagger.
|
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=item AUTOLOAD()ing of methods and non-tagged classes/modules
|
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
If one tries to check tag on non-tagged class/module, there will be no tag method, so call of tag method will raise an exception. This suggests can() or eval{} wrap to be always used as a precaution.
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
Moreover, potential use of AUTOLOAD defeats unique prefixes in tag method names and requires always calling tag method conditional on result of prior can() (eval{} will not help in this case) checking if tag is defined:
|
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
$tag_value = $class->is
|
322
|
|
|
|
|
|
|
if $class->can('is');
|
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
Awesome->is($class);
|
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
Class::Tag solve this problem.
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=item Tagging
|
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
Tagging is essentially defining an attribute. Applying read-only tag to class is simple enough, but applying writable tag or applying tag to blessed-hash objects either ends up in writing accessor or requires use of some attributes construction module, of which Class::Tag is essentially the one:
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
{
|
333
|
|
|
|
|
|
|
package Foo;
|
334
|
|
|
|
|
|
|
my $writable = 'variable';
|
335
|
|
|
|
|
|
|
sub writable { @_ > 1 ? $writable = $_[1] : $writable }
|
336
|
|
|
|
|
|
|
sub instance { @_ > 1 ? $_[0]->{instance} = $_[1] : $_[0]->{instance} }
|
337
|
|
|
|
|
|
|
}
|
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
{
|
340
|
|
|
|
|
|
|
package Foo;
|
341
|
|
|
|
|
|
|
use Class::Tag writable => 'variable', instance => undef;
|
342
|
|
|
|
|
|
|
}
|
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
bless $obj = {}, 'Foo';
|
345
|
|
|
|
|
|
|
Class::Tag->writable('Foo') eq 'variable';
|
346
|
|
|
|
|
|
|
Class::Tag->writable('Foo' => 'new value');
|
347
|
|
|
|
|
|
|
Class::Tag->writable('Foo') eq 'new value';
|
348
|
|
|
|
|
|
|
Class::Tag->instance($foo => 'init value');
|
349
|
|
|
|
|
|
|
Class::Tag->instance('Foo') eq 'init value';
|
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
except Class::Tag's default accessor implements copy-on-write tag values on blessed-hash object instances (and simple tag inheritance from class for blessed-non-hashes), rather than simplistic accessor in above alternative.
|
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=back
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
Class::Tag solves these problems by moving tag constructors and accessors to tagger class, which is far more predictable and controlled environment.
|
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head1 SEE ALSO
|
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
The Class::DOES module provide the ability to use DOES() for tagging classes with role names - see discussion in L"Traditional alternatives">.
|
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=head1 SUPPORT
|
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
Send bug reports, patches, ideas, suggestions, feature requests or any module-related information to L. They are welcome and each carefully considered.
|
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
In particular, if you find certain portions of this documentation either unclear, complicated or incomplete, please let me know, so that I can try to make it better.
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
If you have examples of a neat usage of Class::Tag, drop a line too.
|
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=head1 AUTHOR
|
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
Alexandr Kononoff (L)
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
Copyright (c) 2010 Alexandr Kononoff (L). All rights reserved.
|
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
This program is free software; you can use, redistribute and/or modify it either under the same terms as Perl itself or, at your discretion, under following Simplified (2-clause) BSD License terms:
|
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
382
|
|
|
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=cut
|
387
|
|
|
|
|
|
|
|
388
|
1
|
|
|
1
|
|
7
|
no warnings;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
38
|
|
389
|
|
|
|
|
|
|
|
390
|
1
|
|
|
1
|
|
5
|
use Carp;
|
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
109
|
|
391
|
1
|
|
|
1
|
|
11
|
use Scalar::Util qw(blessed);
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
1211
|
|
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
sub NAMESPACE () { 'aixfHgvpm7hgVziaO' }
|
394
|
|
|
|
|
|
|
|
395
|
237
|
|
|
237
|
|
778
|
sub _tagged_accessor { _subnames( join '_', $_[0], NAMESPACE, $_[1] ) }
|
396
|
|
|
|
|
|
|
|
397
|
237
|
|
|
237
|
|
251
|
sub _subnames { my $a; ($a = $_[0]) =~ s/:/_/g; return $a }
|
|
237
|
|
|
|
|
586
|
|
|
237
|
|
|
|
|
623
|
|
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
*unimport = *untag = __PACKAGE__->new_import('unimport');
|
400
|
|
|
|
|
|
|
*import = *tag = __PACKAGE__->new_import();
|
401
|
|
|
|
|
|
|
import { __PACKAGE__ } 'AUTOLOAD';
|
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub new_import {
|
404
|
14
|
|
|
14
|
0
|
24
|
my (undef, $unimport) = @_;
|
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
return sub{
|
407
|
37
|
|
|
37
|
|
3500
|
my $self = shift;
|
408
|
37
|
|
33
|
|
|
149
|
my $tagger_class = ref($self)||$self;
|
409
|
37
|
|
33
|
|
|
131
|
my $tagged_class =
|
410
|
|
|
|
|
|
|
$Class::Tag::caller||caller;
|
411
|
37
|
|
|
|
|
39
|
$Class::Tag::caller = undef;
|
412
|
|
|
|
|
|
|
|
413
|
37
|
|
|
|
|
38
|
my $tags;
|
414
|
37
|
100
|
|
|
|
150
|
ref $_[0] eq 'HASH'
|
415
|
|
|
|
|
|
|
? ( $tags = $_[0] )
|
416
|
|
|
|
|
|
|
: ( @$tags{ @_ } = (1) x @_ );
|
417
|
|
|
|
|
|
|
|
418
|
37
|
100
|
|
|
|
91
|
%$tags or $tags->{is} = 1;
|
419
|
|
|
|
|
|
|
|
420
|
37
|
|
|
|
|
109
|
foreach my $tag (keys %$tags) {
|
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
# bless()ings below are just for labeling (safe enough as nobody would check ref *GLOB{CODE} eq 'CODE', which becomes false unexpectedly)...
|
423
|
|
|
|
|
|
|
|
424
|
49
|
|
|
|
|
85
|
my $tagged_accessor
|
425
|
|
|
|
|
|
|
= _tagged_accessor($tagger_class, $tag);
|
426
|
49
|
|
|
|
|
145
|
my $tag_value = bless \$tags->{$tag}, $tagger_class;
|
427
|
|
|
|
|
|
|
|
428
|
49
|
|
|
|
|
104
|
my $tagger_accessor = join '::', $tagger_class, $tag;
|
429
|
49
|
|
|
|
|
84
|
my $tagged_accessor2 = join '::', $tagged_class, $tagged_accessor;
|
430
|
49
|
100
|
|
|
|
80
|
if ($unimport) {
|
431
|
8
|
50
|
33
|
|
|
33
|
croak("Error: tag accessor collision - alien $tag() in tagger class $tagger_class")
|
432
|
|
|
|
|
|
|
if *$tagger_accessor{CODE}
|
433
|
|
|
|
|
|
|
and ref *$tagger_accessor{CODE} ne $tagger_class; # means we may have been using alien thing as accessor
|
434
|
|
|
|
|
|
|
|
435
|
8
|
50
|
|
|
|
23
|
undef *$tagger_accessor
|
436
|
|
|
|
|
|
|
and $tagged_class
|
437
|
|
|
|
|
|
|
eq $tagger_class;
|
438
|
|
|
|
|
|
|
|
439
|
8
|
|
|
|
|
28
|
undef *$tagged_accessor2; # has rare name, so safe to unconditionally undef entire glob
|
440
|
|
|
|
|
|
|
}
|
441
|
|
|
|
|
|
|
else {
|
442
|
|
|
|
|
|
|
*$tagged_accessor2 = sub{
|
443
|
173
|
100
|
|
173
|
|
530
|
@_ > 1
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
444
|
|
|
|
|
|
|
? ( _ref_type($_[0]) eq 'HASH'
|
445
|
|
|
|
|
|
|
? bless \($_[0]->{$tagger_accessor} = $_[1]), $tagger_class
|
446
|
|
|
|
|
|
|
: \($$tag_value = $_[1]) )
|
447
|
|
|
|
|
|
|
: ( _ref_type($_[0]) eq 'HASH'
|
448
|
|
|
|
|
|
|
? exists $_[0]->{$tagger_accessor}
|
449
|
|
|
|
|
|
|
? bless \$_[0]->{$tagger_accessor}, $tagger_class
|
450
|
|
|
|
|
|
|
: $tag_value
|
451
|
|
|
|
|
|
|
: $tag_value )
|
452
|
41
|
|
|
|
|
334
|
};
|
453
|
|
|
|
|
|
|
|
454
|
41
|
100
|
|
|
|
89
|
if ( $tagged_class
|
455
|
|
|
|
|
|
|
eq $tagger_class) {
|
456
|
7
|
50
|
33
|
|
|
28
|
*$tagger_accessor{CODE} and ref
|
457
|
|
|
|
|
|
|
*$tagger_accessor{CODE} ne $tagger_class and croak("Error: tag accessor collision - tagger class $tagger_class already defines or stubs $tag()");
|
458
|
|
|
|
|
|
|
*$tagger_accessor{CODE} or
|
459
|
|
|
|
|
|
|
*$tagger_accessor = bless sub{
|
460
|
|
|
|
|
|
|
|
461
|
214
|
|
|
214
|
|
7133
|
my $sub_accessor;
|
462
|
214
|
100
|
100
|
|
|
1082
|
unless (@_ == 2 and $_[0] eq $_[1]) {
|
463
|
107
|
100
|
|
|
|
294
|
local $Class::Tag::AUTOLOAD
|
464
|
|
|
|
|
|
|
= 'AUTOLOAD'
|
465
|
|
|
|
|
|
|
if $tag eq 'AUTOLOAD';
|
466
|
107
|
|
|
|
|
318
|
$sub_accessor = $tagger_class->$tag($tagger_class);
|
467
|
|
|
|
|
|
|
}
|
468
|
|
|
|
|
|
|
|
469
|
214
|
100
|
66
|
|
|
1349
|
unshift @_, undef # if called as function
|
|
|
|
66
|
|
|
|
|
470
|
|
|
|
|
|
|
unless @_ > 1
|
471
|
|
|
|
|
|
|
and ref($_[0])||$_[0] eq $tagger_class;
|
472
|
|
|
|
|
|
|
|
473
|
214
|
100
|
|
|
|
496
|
goto &$sub_accessor
|
474
|
|
|
|
|
|
|
if ref $sub_accessor eq 'CODE';
|
475
|
|
|
|
|
|
|
|
476
|
202
|
50
|
66
|
|
|
1177
|
ref $_[1]
|
477
|
|
|
|
|
|
|
or $_[1] =~ /^\w[\w\:]*$/
|
478
|
|
|
|
|
|
|
or return undef;
|
479
|
|
|
|
|
|
|
#or croak("Error: No valid class specified as first argument: '$_[1]'");
|
480
|
|
|
|
|
|
|
|
481
|
202
|
|
|
|
|
291
|
my $tagged_accessor
|
482
|
|
|
|
|
|
|
= $tagged_accessor;
|
483
|
202
|
100
|
|
|
|
417
|
if ($tag eq 'AUTOLOAD') {
|
484
|
188
|
|
|
|
|
604
|
(my $AUTOLOAD = $Class::Tag::AUTOLOAD) =~ s/^.*:://;
|
485
|
188
|
|
|
|
|
369
|
$tagged_accessor =
|
486
|
|
|
|
|
|
|
_tagged_accessor($tagger_class, $AUTOLOAD);
|
487
|
|
|
|
|
|
|
}
|
488
|
|
|
|
|
|
|
|
489
|
198
|
|
|
|
|
208
|
my $scalar_value = defined $_[0] # called as method
|
490
|
198
|
100
|
33
|
|
|
1420
|
? &{ shift; $_[0]->can($tagged_accessor) or return undef }
|
|
4
|
|
|
|
|
36
|
|
491
|
202
|
100
|
66
|
|
|
1137
|
: &{*{join '::', ref($_[1])||$_[1], $tagged_accessor}{CODE} or return undef }
|
|
4
|
100
|
33
|
|
|
6
|
|
|
|
50
|
33
|
|
|
|
|
492
|
|
|
|
|
|
|
if $_[1] and (!ref $_[1] or blessed($_[1]))
|
493
|
|
|
|
|
|
|
or croak("Querying tag of untagable $_[1]");
|
494
|
173
|
50
|
|
|
|
846
|
return ref $scalar_value eq $tagger_class ? $$scalar_value : undef
|
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
}
|
497
|
7
|
50
|
|
|
|
84
|
, $tagger_class;
|
498
|
|
|
|
|
|
|
}
|
499
|
|
|
|
|
|
|
else {
|
500
|
34
|
100
|
100
|
|
|
704
|
$tagger_class->isa( ref
|
501
|
|
|
|
|
|
|
$tagger_class->can($tag) ) or
|
502
|
|
|
|
|
|
|
$tagger_class->isa( ref
|
503
|
|
|
|
|
|
|
$tagger_class->can('AUTOLOAD') )
|
504
|
|
|
|
|
|
|
or croak("Error: tagger class $tagger_class declares no '$tag' tag: ", $tagged_class);
|
505
|
|
|
|
|
|
|
}
|
506
|
|
|
|
|
|
|
}
|
507
|
|
|
|
|
|
|
|
508
|
48
|
100
|
|
|
|
5584
|
if ($tag eq 'tagger_class') {
|
509
|
|
|
|
|
|
|
|
510
|
6
|
|
|
|
|
7
|
my $new_tagger_class = $tagged_class;
|
511
|
6
|
|
100
|
|
|
36
|
$INC{ join '/', split '::', "$new_tagger_class.pm" } ||= 1; # support inlined tag classes
|
512
|
6
|
|
|
|
|
13
|
my $new_import = join '::', $new_tagger_class, 'import';
|
513
|
6
|
|
|
|
|
10
|
my $new_import2 = join '::', $new_tagger_class, 'tag';
|
514
|
6
|
|
|
|
|
17
|
my $sub_import = *$new_import{CODE};
|
515
|
6
|
|
|
|
|
16
|
my $sub_import2 = *$new_import2{CODE};
|
516
|
6
|
|
|
|
|
10
|
my $new_unimport = join '::', $new_tagger_class, 'unimport';
|
517
|
6
|
|
|
|
|
8
|
my $new_unimport2 = join '::', $new_tagger_class, 'untag';
|
518
|
6
|
|
|
|
|
17
|
my $sub_unimport = *$new_unimport{CODE};
|
519
|
6
|
|
|
|
|
15
|
my $sub_unimport2 = *$new_unimport2{CODE};
|
520
|
|
|
|
|
|
|
|
521
|
6
|
50
|
|
|
|
11
|
if ($unimport) {
|
522
|
|
|
|
|
|
|
}
|
523
|
|
|
|
|
|
|
else {
|
524
|
|
|
|
|
|
|
my $sub_new_import = sub{
|
525
|
0
|
|
|
0
|
|
0
|
my ($sub_import, $sub_wasimport) = @_;
|
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
return #bless
|
528
|
|
|
|
|
|
|
! $sub_wasimport
|
529
|
|
|
|
|
|
|
? $sub_import
|
530
|
|
|
|
|
|
|
: sub{
|
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
#goto &$sub_import;
|
533
|
|
|
|
|
|
|
|
534
|
0
|
|
|
|
|
0
|
local $Class::Tag::caller = caller; # let &$sub_import know original caller...
|
535
|
|
|
|
|
|
|
# &$sub_import;
|
536
|
0
|
|
|
|
|
0
|
&$sub_import(@_);
|
537
|
0
|
0
|
|
|
|
0
|
goto &$sub_wasimport
|
538
|
|
|
|
|
|
|
if $sub_wasimport;
|
539
|
0
|
0
|
|
|
|
0
|
};
|
540
|
|
|
|
|
|
|
#, $tagger_class;
|
541
|
6
|
|
|
|
|
27
|
};
|
542
|
|
|
|
|
|
|
|
543
|
6
|
|
|
|
|
16
|
*$new_import =
|
544
|
|
|
|
|
|
|
*$new_import2
|
545
|
|
|
|
|
|
|
= __PACKAGE__->new_import();
|
546
|
|
|
|
|
|
|
|
547
|
6
|
|
|
|
|
21
|
*$new_unimport =
|
548
|
|
|
|
|
|
|
*$new_unimport2
|
549
|
|
|
|
|
|
|
= __PACKAGE__->new_import('unimport');
|
550
|
|
|
|
|
|
|
}
|
551
|
|
|
|
|
|
|
}
|
552
|
|
|
|
|
|
|
}
|
553
|
|
|
|
|
|
|
}
|
554
|
14
|
|
|
|
|
346
|
}
|
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
sub _ref_type {
|
557
|
173
|
100
|
|
173
|
|
639
|
return undef if !ref $_[0];
|
558
|
26
|
50
|
|
|
|
281
|
return $1 if $_[0] =~ /=(\w+)/;
|
559
|
0
|
|
|
|
|
|
return ref $_[0]
|
560
|
|
|
|
|
|
|
}
|
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
1;
|
563
|
|
|
|
|
|
|
|