File Coverage

lib/Data/Identifier.pm
Criterion Covered Total %
statement 220 419 52.5
branch 113 310 36.4
condition 74 192 38.5
subroutine 25 36 69.4
pod 22 22 100.0
total 454 979 46.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             # Copyright (c) 2023-2026 Philipp Schafft
4              
5             # licensed under Artistic License 2.0 (see LICENSE file)
6              
7             # ABSTRACT: format independent identifier object
8              
9              
10             package Data::Identifier;
11              
12 7     7   1262881 use v5.20;
  7         18  
13 7     7   27 use strict;
  7         6  
  7         145  
14 7     7   21 use warnings;
  7         13  
  7         310  
15              
16 7     7   36 use parent qw(Data::Identifier::Interface::Known Data::Identifier::Interface::Userdata);
  7         9  
  7         65  
17              
18 7     7   352 use Carp;
  7         9  
  7         336  
19 7     7   9802 use Math::BigInt lib => 'GMP';
  7         231068  
  7         27  
20 7     7   161459 use URI;
  7         31671  
  7         2429  
21              
22             our $VERSION = v0.29;
23              
24             use constant {
25 7         1053 RE_UUID => qr/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z/,
26             RE_OID => qr/^[0-2](?:\.(?:0|[1-9][0-9]*))+\z/,
27             RE_URI => qr/^[a-zA-Z][a-zA-Z0-9\+\.\-]+:/,
28             RE_UINT => qr/^(?:0|[1-9][0-9]*)\z/,
29             RE_SINT => qr/^(?:0|-?[1-9][0-9]*)\z/,
30             RE_QID => qr/^[QPL][1-9][0-9]*\z/,
31             RE_DOI => qr/^10\.[1-9][0-9]+(?:\.[0-9]+)*\/./,
32             RE_GTIN => qr/^[0-9]{8}(?:[0-9]{4,6})?\z/,
33             RE_UNICODE => qr/^U\+([0-9A-F]{4,7})\z/,
34             RE_SIMPLE_TAG => qr/^[^\p{upper case}\s]+\z/,
35 7     7   46 };
  7         9  
36              
37             use constant {
38 7         38589 WK_NULL => '00000000-0000-0000-0000-000000000000', # NULL, undef, ...
39             WK_UUID => '8be115d2-dc2f-4a98-91e1-a6e3075cbc31', # uuid
40             WK_OID => 'd08dc905-bbf6-4183-b219-67723c3c8374', # oid
41             WK_URI => 'a8d1637d-af19-49e9-9ef8-6bc1fbcf6439', # uri
42             WK_SID => 'f87a38cb-fd13-4e15-866c-e49901adbec5', # small-identifier
43             WK_WD => 'ce7aae1e-a210-4214-926a-0ebca56d77e3', # wikidata-identifier
44             WK_GTIN => '82d529be-0f00-4b4f-a43f-4a22de5f5312', # gtin
45             WK_IBAN => 'b1418262-6bc9-459c-b4b0-a054d77db0ea', # iban
46             WK_BIC => 'c8a3a132-f160-473c-b5f3-26a748f37e62', # bic
47             WK_DOI => '931f155e-5a24-499b-9fbb-ed4efefe27fe', # doi
48             WK_FC => 'd576b9d1-47d4-43ae-b7ec-bbea1fe009ba', # factgrid-identifier
49             WK_UNICODE_CP => '5f167223-cc9c-4b2f-9928-9fe1b253b560', # unicode-code-point
50             WK_SNI => '039e0bb7-5dd3-40ee-a98c-596ff6cce405', # sirtx-numerical-identifier
51             WK_HDI => 'f8eb04ef-3b8a-402c-ad7c-1e6814cb1998', # host-defined-identifier
52             WK_UDI => '05af99f9-4578-4b79-aabe-946d8e6f5888', # user-defined-identifier
53             WK_CHAT0W => '2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a', # chat-0-word-identifier
54              
55             NS_WD => '9e10aca7-4a99-43ac-9368-6cbfa43636df', # Wikidata-namespace
56             NS_FC => '6491f7a9-0b29-4ef1-992c-3681cea18182', # factgrid-namespace
57             NS_INT => '5dd8ddbb-13a8-4d6c-9264-36e6dd6f9c99', # integer-namespace
58             NS_DATE => 'fc43fbba-b959-4882-b4c8-90a288b7d416', # gregorian-date-namespace
59             NS_GTIN => 'd95d8b1f-5091-4642-a6b0-a585313915f1', # gtin-namespace
60             NS_UNICODE_CP => '132aa723-a373-48bf-a88d-69f1e00f00cf', # 'unicode-character-namespace'
61 7     7   27 };
  7         10  
62              
63             # Features:
64             my $enabled_oid = 1;
65              
66             my %uuid_to_uriid_org = (
67             WK_UUID() => 'uuid',
68             WK_OID() => 'oid',
69             WK_URI() => 'uri',
70             WK_SID() => 'sid',
71             WK_GTIN() => 'gtin',
72             WK_WD() => 'wikidata-identifier',
73             );
74              
75             my %uuid_org_to_uuid = map {$uuid_to_uriid_org{$_} => $_} keys %uuid_to_uriid_org;
76              
77             my $well_known_uuid = __PACKAGE__->new(ise => WK_UUID, validate => RE_UUID);
78              
79             my %well_known = (
80             uuid => $well_known_uuid,
81             oid => __PACKAGE__->new($well_known_uuid => WK_OID, validate => RE_OID),
82             uri => __PACKAGE__->new($well_known_uuid => WK_URI, validate => RE_URI),
83             sid => __PACKAGE__->new($well_known_uuid => WK_SID, validate => RE_UINT),
84             sni => __PACKAGE__->new($well_known_uuid => WK_SNI, validate => RE_UINT),
85             wd => __PACKAGE__->new($well_known_uuid => WK_WD, validate => RE_QID, namespace => NS_WD, generate => 'id-based'),
86             fc => __PACKAGE__->new($well_known_uuid => WK_FC, validate => RE_QID, namespace => NS_FC, generate => 'id-based'),
87             gtin => __PACKAGE__->new($well_known_uuid => WK_GTIN, validate => RE_GTIN, namespace => NS_GTIN, generate => 'id-based'),
88             iban => __PACKAGE__->new($well_known_uuid => WK_IBAN),
89             bic => __PACKAGE__->new($well_known_uuid => WK_BIC),
90             doi => __PACKAGE__->new($well_known_uuid => WK_DOI, validate => RE_DOI),
91              
92             # Unofficial, not part of public API:
93             # Also used by Data::Identifier::Util!
94             unicodecp => __PACKAGE__->new($well_known_uuid => WK_UNICODE_CP, validate => RE_UNICODE, namespace => NS_UNICODE_CP, generate => 'id-based'),
95              
96             hdi => __PACKAGE__->new($well_known_uuid => WK_HDI, validate => RE_UINT),
97             udi => __PACKAGE__->new($well_known_uuid => WK_UDI, validate => RE_UINT),
98             null => __PACKAGE__->new($well_known_uuid => WK_NULL),
99             );
100              
101             my %registered;
102              
103             $_->register foreach values %well_known;
104              
105             # Refill with sids:
106             {
107             my %wk_sids = (
108             WK_NULL() => 0, # NULL
109             'ddd60c5c-2934-404f-8f2d-fcb4da88b633' => 1, # also-shares-identifier
110             WK_UUID() => 2,
111             'bfae7574-3dae-425d-89b1-9c087c140c23' => 3, # tagname
112             '7f265548-81dc-4280-9550-1bd0aa4bf748' => 4, # has-type
113             WK_URI() => 5,
114             WK_OID() => 6,
115             # Unassigned: 7
116             'd0a4c6e2-ce2f-4d4c-b079-60065ac681f1' => 8, # language-tag-identifier
117             WK_WD() => 9,
118             '923b43ae-a50e-4db3-8655-ed931d0dd6d4' => 10, # specialises
119             'eacbf914-52cf-4192-a42c-8ecd27c85ee1' => 11, # unicode-string
120             '928d02b0-7143-4ec9-b5ac-9554f02d3fb1' => 12, # integer
121             'dea3782c-6bcb-4ce9-8a39-f8dab399d75d' => 13, # unsigned-integer
122             # Unassigned: 14, 15
123             '6ba648c2-3657-47c2-8541-9b73c3a9b2b4' => 16, # default-context
124             '52a516d0-25d8-47c7-a6ba-80983e576c54' => 17, # proto-file
125             '1cd4a6c6-0d7c-48d1-81e7-4e8d41fdb45d' => 18, # final-file-size
126             '6085f87e-4797-4bb2-b23d-85ff7edc1da0' => 19, # text-fragment
127             '4c9656eb-c130-42b7-9348-a1fee3f42050' => 20, # also-list-contains-also
128             '298ef373-9731-491d-824d-b2836250e865' => 21, # proto-message
129             '7be4d8c7-6a75-44cc-94f7-c87433307b26' => 22, # proto-entity
130             '65bb36f2-b558-48af-8512-bca9150cca85' => 23, # proxy-type
131             'a1c478b5-0a85-4b5b-96da-d250db14a67c' => 24, # flagged-as
132             '59cfe520-ba32-48cc-b654-74f7a05779db' => 25, # marked-as
133             '2bffc55d-7380-454e-bd53-c5acd525d692' => 26, # roaraudio-error-number
134             WK_SID() => 27,
135             'd2750351-aed7-4ade-aa80-c32436cc6030' => 28, # also-has-role
136             '11d8962c-0a71-4d00-95ed-fa69182788a8' => 29, # also-has-comment
137             '30710bdb-6418-42fb-96db-2278f3bfa17f' => 30, # also-has-description
138             # Unassigned: 31
139             '448c50a8-c847-4bc7-856e-0db5fea8f23b' => 32, # final-file-encoding
140             '79385945-0963-44aa-880a-bca4a42e9002' => 33, # final-file-hash
141             '3fde5688-6e34-45e9-8f33-68f079b152c8' => 34, # SEEK_SET
142             'bc598c52-642e-465b-b079-e9253cd6f190' => 35, # SEEK_CUR
143             '06aff30f-70e8-48b4-8b20-9194d22fc460' => 36, # SEEK_END
144             '59a5691a-6a19-4051-bc26-8db82c019df3' => 37, # inode
145             WK_CHAT0W() => 112, # chat-0-word-identifier
146             WK_SNI() => 113, # sirtx-numerical-identifier
147             WK_GTIN() => 160,
148             );
149              
150             foreach my $ise (keys %wk_sids) {
151             my $identifier = __PACKAGE__->new(ise => $ise);
152             $identifier->{id_cache} //= {};
153             $identifier->{id_cache}->{WK_SID()} //= $wk_sids{$ise};
154             $identifier->register; # re-register
155             }
156             }
157              
158             # Refill with snis:
159             {
160             my %wk_snis = (
161             WK_NULL() => 0, # NULL
162             '039e0bb7-5dd3-40ee-a98c-596ff6cce405' => 10, # sirtx-numerical-identifier
163             'f87a38cb-fd13-4e15-866c-e49901adbec5' => 115, # small-identifier
164             '2bffc55d-7380-454e-bd53-c5acd525d692' => 116, # roaraudio-error-number
165             WK_CHAT0W() => 118, # chat-0-word-identifier
166             WK_UUID() => 119,
167             WK_OID() => 120,
168             WK_URI() => 121,
169             WK_WD() => 123,
170             );
171              
172             foreach my $ise (keys %wk_snis) {
173             my $identifier = __PACKAGE__->new(ise => $ise);
174             $identifier->{id_cache} //= {};
175             $identifier->{id_cache}->{WK_SNI()} //= $wk_snis{$ise};
176             $identifier->register; # re-register
177             }
178             }
179              
180             # Update NULL:
181             {
182             my $identifier = __PACKAGE__->new(uuid => WK_NULL);
183             $identifier->{id_cache} //= {};
184             foreach my $type (WK_HDI, WK_CHAT0W) {
185             $identifier->{id_cache}->{$type} //= 0;
186             }
187             $identifier->register;
188             }
189              
190             # Some extra tags such as namespaces:
191             foreach my $ise (NS_WD, NS_INT, NS_DATE) {
192             my $identifier = __PACKAGE__->new(ise => $ise);
193             $identifier->register; # re-register
194             }
195              
196             # Refill with displaynames
197             {
198             my %displaynames = (
199             WK_NULL() => 'null',
200             WK_UUID() => 'uuid',
201             WK_OID() => 'oid',
202             WK_URI() => 'uri',
203             WK_SID() => 'small-identifier',
204             WK_WD() => 'wikidata-identifier',
205             WK_GTIN() => 'gtin',
206             WK_IBAN() => 'iban',
207             WK_BIC() => 'bic',
208             WK_DOI() => 'doi',
209             WK_FC() => 'factgrid-identifier',
210             WK_UNICODE_CP() => 'unicode-code-point',
211             WK_SNI() => 'sirtx-numerical-identifier',
212             WK_HDI() => 'host-defined-identifier',
213             WK_UDI() => 'user-defined-identifier',
214             WK_CHAT0W() => 'chat-0-word-identifier',
215             NS_WD() => 'Wikidata-namespace',
216             NS_FC() => 'factgrid-namespace',
217             NS_INT() => 'integer-namespace',
218             NS_DATE() => 'gregorian-date-namespace',
219             NS_UNICODE_CP() => 'unicode-character-namespace',
220              
221             'ddd60c5c-2934-404f-8f2d-fcb4da88b633' => 'also-shares-identifier',
222             'bfae7574-3dae-425d-89b1-9c087c140c23' => 'tagname',
223             '7f265548-81dc-4280-9550-1bd0aa4bf748' => 'has-type',
224             'd0a4c6e2-ce2f-4d4c-b079-60065ac681f1' => 'language-tag-identifier',
225             '923b43ae-a50e-4db3-8655-ed931d0dd6d4' => 'specialises',
226             'eacbf914-52cf-4192-a42c-8ecd27c85ee1' => 'unicode-string',
227             '928d02b0-7143-4ec9-b5ac-9554f02d3fb1' => 'integer',
228             'dea3782c-6bcb-4ce9-8a39-f8dab399d75d' => 'unsigned-integer',
229             '6ba648c2-3657-47c2-8541-9b73c3a9b2b4' => 'default-context',
230             '52a516d0-25d8-47c7-a6ba-80983e576c54' => 'proto-file',
231             '1cd4a6c6-0d7c-48d1-81e7-4e8d41fdb45d' => 'final-file-size',
232             '6085f87e-4797-4bb2-b23d-85ff7edc1da0' => 'text-fragment',
233             '4c9656eb-c130-42b7-9348-a1fee3f42050' => 'also-list-contains-also',
234             '298ef373-9731-491d-824d-b2836250e865' => 'proto-message',
235             '7be4d8c7-6a75-44cc-94f7-c87433307b26' => 'proto-entity',
236             '65bb36f2-b558-48af-8512-bca9150cca85' => 'proxy-type',
237             'a1c478b5-0a85-4b5b-96da-d250db14a67c' => 'flagged-as',
238             '59cfe520-ba32-48cc-b654-74f7a05779db' => 'marked-as',
239             '2bffc55d-7380-454e-bd53-c5acd525d692' => 'roaraudio-error-number',
240             'd2750351-aed7-4ade-aa80-c32436cc6030' => 'also-has-role',
241             '11d8962c-0a71-4d00-95ed-fa69182788a8' => 'also-has-comment',
242             '30710bdb-6418-42fb-96db-2278f3bfa17f' => 'also-has-description',
243             '448c50a8-c847-4bc7-856e-0db5fea8f23b' => 'final-file-encoding',
244             '79385945-0963-44aa-880a-bca4a42e9002' => 'final-file-hash',
245             '3fde5688-6e34-45e9-8f33-68f079b152c8' => 'SEEK_SET',
246             'bc598c52-642e-465b-b079-e9253cd6f190' => 'SEEK_CUR',
247             '06aff30f-70e8-48b4-8b20-9194d22fc460' => 'SEEK_END',
248             '59a5691a-6a19-4051-bc26-8db82c019df3' => 'inode',
249             '53863a15-68d4-448d-bd69-a9b19289a191' => 'unsigned-integer-generator',
250             'e8aa9e01-8d37-4b4b-8899-42ca0a2a906f' => 'signed-integer-generator',
251             'd74f8c35-bcb8-465c-9a77-01010e8ed25c' => 'unicode-character-generator',
252             '55febcc4-6655-4397-ae3d-2353b5856b34' => 'rgb-colour-generator',
253             '97b7f241-e1c5-4f02-ae3c-8e31e501e1dc' => 'date-generator',
254             '19659233-0a22-412c-bdf1-8ee9f8fc4086' => 'multiplicity-generator',
255             '5ec197c3-1406-467c-96c7-4b1a6ec2c5c9' => 'minimum-multiplicity-generator',
256             );
257              
258             foreach my $ise (keys %displaynames) {
259             my $identifier = __PACKAGE__->new(ise => $ise);
260             $identifier->{displayname} //= $displaynames{$ise};
261             $identifier->register; # re-register
262             }
263             }
264              
265             {
266             # ISE -> namespace
267             my %namespaces_uint = (
268             '4a7fc2e2-854b-42ec-b24f-c7fece371865' => 'ac59062c-6ba2-44de-9f54-09e28f2c0b5c', # e621-post-identifier: e621-post-namespace
269             'a0a4fae2-be6f-4a51-8326-6110ba845a16' => '69b7ff38-ca78-43a8-b9ea-66cb02312eef', # e621-pool-identifier: e621-pool-namespace
270             '6e3590b6-2a0c-4850-a71f-8ba196a52280' => 'b96e5d94-0767-40fa-9864-5977eb507ae0', # danbooru2chanjp-post-identifier: danbooru2chanjp-post-namespace
271             );
272             my %namespaces_sint = (
273             '2bffc55d-7380-454e-bd53-c5acd525d692' => '744eaf4e-ae93-44d8-9ab5-744105222da6', # roaraudio-error-number: roaraudio-error-namespace
274             );
275             my %namespaces_simple_tag = (
276             '6fe0dbf0-624b-48b3-b558-0394c14bad6a' => '3623de4d-0dd4-4236-946a-2613467d50f1', # e621tag: e621tag-namespace
277             'c5632c60-5da2-41af-8b60-75810b622756' => '93f2c36b-8cb6-4f2c-924b-98188f224235', # danbooru2chanjp-tag: danbooru2chanjp-tag-namespace
278             );
279              
280             foreach my $ise (keys %namespaces_uint) {
281             my $identifier = __PACKAGE__->new(ise => $ise);
282             $identifier->{namespace} //= __PACKAGE__->new(ise => $namespaces_uint{$ise});
283             $identifier->{validate} //= RE_UINT;
284             $identifier->{generate} //= 'id-based';
285             $identifier->register; # re-register
286             }
287              
288             foreach my $ise (keys %namespaces_sint) {
289             my $identifier = __PACKAGE__->new(ise => $ise);
290             $identifier->{namespace} //= __PACKAGE__->new(ise => $namespaces_sint{$ise});
291             $identifier->{validate} //= RE_SINT;
292             $identifier->{generate} //= 'id-based';
293             $identifier->register; # re-register
294             }
295              
296             foreach my $ise (keys %namespaces_simple_tag) {
297             my $identifier = __PACKAGE__->new(ise => $ise);
298             $identifier->{namespace} //= __PACKAGE__->new(ise => $namespaces_simple_tag{$ise});
299             $identifier->{validate} //= RE_SIMPLE_TAG;
300             $identifier->{generate} //= 'id-based';
301             $identifier->register; # re-register
302             }
303              
304             # validate => RE_QID, namespace => NS_FC, generate => 'id-based'
305             }
306              
307             # Call this after after we loaded all our stuff and before anyone else will register stuff:
308             __PACKAGE__->_known_provider('wellknown');
309              
310              
311             sub new {
312 1219     1219 1 7588 my ($pkg, $type, $id, %opts) = @_;
313 1219         1435 my $self = bless {};
314              
315 1219 50       1646 croak 'No type given' unless defined $type;
316 1219 50       1511 croak 'No id given' unless defined $id;
317              
318 1219 100 100     2633 if (!ref($type) && $type eq 'from') {
319 97 50       139 if (ref($id)) {
320 0         0 my $from = $id;
321 0 0 0     0 if ($id->isa('Data::Identifier')) {
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
322 0 0       0 if (scalar(keys %opts)) {
323 0         0 $type = $id->type;
324 0         0 $id = $id->id;
325             } else {
326 0         0 return $id;
327             }
328             } elsif ($id->isa('URI')) {
329 0         0 $type = 'uri';
330             } elsif ($id->isa('Mojo::URL')) {
331 0         0 $type = 'uri';
332 0         0 $id = $id->to_string;
333             } elsif ($id->isa('Data::URIID::Result')) {
334 0   0 0   0 $opts{displayname} //= sub { return $from->attribute('displayname', default => undef) };
  0         0  
335 0         0 $type = $id->id_type;
336 0         0 $id = $id->id;
337             } elsif ($id->isa('Data::URIID::Base') || $id->isa('Data::URIID::Colour') || $id->isa('Data::URIID::Service')) {
338             #$opts{displayname} //= $id->name if $id->isa('Data::URIID::Service');
339 0   0     0 $opts{displayname} //= $id->displayname(default => undef, no_defaults => 1);
340 0         0 $type = 'ise';
341 0         0 $id = $id->ise;
342             } elsif ($id->isa('Data::TagDB::Tag')) {
343 0   0 0   0 $opts{displayname} //= sub { $from->displayname };
  0         0  
344 0         0 $type = 'ise';
345 0         0 $id = $id->ise;
346             } elsif ($id->isa('File::FStore::File') || $id->isa('File::FStore::Adder') || $id->isa('File::FStore::Base')) {
347 0         0 $type = 'ise';
348 0         0 $id = $id->contentise;
349             } elsif ($id->isa('SIRTX::Datecode')) {
350 0         0 $id = $id->as('Data::Identifier');
351 0 0       0 unless (scalar(keys %opts)) {
352 0         0 return $id->as('Data::Identifier');
353             }
354 0         0 $type = $id->type;
355 0         0 $id = $id->id;
356             } elsif ($id->isa('Business::ISBN')) {
357 0         0 $type = $well_known{gtin};
358 0         0 $id = $id->as_isbn13->as_string([]);
359             } elsif ($id->isa('Data::Identifier::Interface::Simple')) {
360             # TODO: We cannot call $id->as('Data::Identifier') here as much as that would be fun,
361             # as this might in turn call exactly this code again resulting in a deep recursion.
362             # A future version might come up with some trick here.
363 0         0 $type = 'ise';
364 0         0 $id = $id->ise;
365             } else {
366 0         0 croak 'Unsupported input data';
367             }
368             } else {
369             # If it's not a ref, try as ise.
370 97         117 $type = 'ise';
371             }
372             }
373              
374 1219 100 100     2402 if (!ref($type) && $type eq 'ise') {
375 923 50       1097 croak 'Undefined identifier but type is ISE' unless defined $id;
376              
377 923 50       3028 if ($id =~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/) { # allow less normalised form than RE_UUID
    0          
    0          
378 923         816 $type = $well_known_uuid;
379              
380             # For bootstrap only.
381 923 100 66     1390 if (!defined($type) && $id eq '8be115d2-dc2f-4a98-91e1-a6e3075cbc31') {
382 7         69 $self->{type} = $well_known_uuid = $type = $self;
383 7         12 $self->{id} = $id;
384             }
385             } elsif ($id =~ RE_OID) {
386 0         0 $type = 'oid';
387             } elsif ($id =~ RE_URI) {
388 0         0 $type = 'uri';
389             } else {
390 0         0 croak 'Not a valid ISE identifier';
391             }
392             }
393              
394 1219 100       1467 unless (ref $type) {
395 197 100       414 if ($type =~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/) { # allow less normalised form than RE_UUID
    100          
396 6         12 $type = $pkg->new(uuid => $type);
397 6         12 $type->register;
398             } elsif ($type eq 'wellknown') {
399 8         68 $self = $well_known{$id};
400 8 50       20 croak 'Unknown well-known' unless defined $self;
401 8         29 return $self;
402             } else {
403 183         300 $type = $well_known{$type};
404             }
405 189 50       246 croak 'Unknown type name' unless defined $type;
406             }
407              
408 1211 50       2200 croak 'Not a valid type' unless $type->isa(__PACKAGE__);
409              
410             # we normalise URIs first as they may then normalised again
411 1211 100 100     2355 if ($type == ($well_known{uri} // 0)) {
412 3         6 my $uri = $id.''; # force stringification
413              
414 3 100       41 if ($uri =~ m#^urn:uuid:([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})$#) {
    50          
    50          
    50          
    50          
415 1         3 $id = $1;
416 1         1 $type = $well_known_uuid;
417             } elsif ($uri =~ m#^urn:oid:([0-2](?:\.(?:0|[1-9][0-9]*))+)$#) {
418 0         0 $id = $1;
419 0         0 $type = $well_known{oid};
420             } elsif ($uri =~ m#^https?://www\.wikidata\.org/entity/([QPL][1-9][0-9]*)$#) {
421 0         0 $id = $1;
422 0         0 $type = $well_known{wd};
423             } elsif ($uri =~ m#^https?://doi\.org/(10\..+)$#) {
424 0         0 $id = $1;
425 0         0 $type = $well_known{doi};
426             } elsif ($uri =~ m#^https?://uriid\.org/([^/]+)/[^/]+#) {
427 2         7 my $ptype = $1;
428 2 50 66     16 if (defined($uuid_org_to_uuid{$ptype}) || $ptype =~ RE_UUID) {
429 2         11 my $u = URI->new($uri);
430 2         8239 my @path_segments = $u->path_segments;
431 2 50 33     130 if (scalar(@path_segments) == 3 && $path_segments[0] eq '') {
432 2   66     13 $type = $pkg->new(uuid => ($uuid_org_to_uuid{$path_segments[1]} // $path_segments[1]));
433 2         9 $id = $path_segments[2];
434             }
435             }
436             }
437             }
438              
439 1211 100 50     1990 if ($type == ($well_known_uuid // 0)) {
    100 50        
440 1131         1461 $id = lc($id); # normalise
441             } elsif ($type == ($well_known{oid} // 0)) {
442 1 50       6 if ($id =~ /^2\.25\.([1-9][0-9]*)$/) {
443 1         4 my $hex = Math::BigInt->new($1)->as_hex;
444 1         519 $hex =~ s/^0x//;
445 1         4 $hex = ('0' x (32 - length($hex))) . $hex;
446 1         20 $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12})$/$1-$2-$3-$4-$5/;
447 1         1 $type = $well_known_uuid;
448 1         3 $id = $hex;
449             }
450             }
451              
452 1211 100       1478 if (defined(my $v = $registered{$type->uuid}{$id})) {
453 594         1161 return $v;
454             }
455              
456              
457 617 100       895 if (defined $type->{validate}) {
458 608 50       3959 croak 'Identifier did not validate against type' unless $id =~ $type->{validate};
459             }
460              
461 617         995 $self->{type} = $type;
462 617         655 $self->{id} = $id;
463              
464 617         650 foreach my $key (qw(validate namespace generator request generate displayname)) {
465 3702 100       4382 next unless defined $opts{$key};
466 228   66     532 $self->{$key} //= $opts{$key};
467             }
468              
469 617         552 foreach my $key (qw(namespace generator)) {
470 1234 100       1729 if (defined(my $v = $self->{$key})) {
471 31 50       40 unless (ref $v) {
472 31         68 $self->{$key} = $pkg->new(from => $v);
473             }
474             }
475             }
476              
477 617 50       839 if (defined(my $tagname = $opts{tagname})) {
478 0 0       0 $tagname = [$tagname] unless ref $tagname;
479 0         0 $tagname = [grep {defined} @{$tagname}];
  0         0  
  0         0  
480 0 0       0 if (scalar(@{$tagname})) {
  0         0  
481 0         0 $self->{tagname} = $tagname;
482             }
483             }
484              
485 617         1147 return bless $self;
486             }
487              
488              
489             #@returns __PACKAGE__
490             sub random {
491 0     0 1 0 my ($pkg, %opts) = @_;
492 0   0     0 my $type = $opts{type} // 'uuid';
493              
494 0 0       0 if (ref $type) {
495 0 0       0 if ($type == $well_known_uuid) {
496 0         0 $type = 'uuid';
497             } else {
498 0         0 croak 'Invalid/Unsupported type';
499             }
500             }
501              
502 0 0 0     0 if ($type ne 'ise' && $type ne 'uuid') {
503 0         0 croak 'Invalid/Unsupported type';
504             }
505              
506 0         0 require Data::Identifier::Generate;
507 0         0 my $uuid = Data::Identifier::Generate->_random(%opts{'sources'});
508 0         0 return $pkg->new(uuid => $uuid, %opts{'displayname'});
509             }
510              
511              
512              
513             #@deprecated
514             sub wellknown {
515 0     0 1 0 my ($pkg, @args) = @_;
516 0         0 return $pkg->known('wellknown', @args);
517             }
518              
519              
520             #@returns __PACKAGE__
521             sub type {
522 46     46 1 1926 my ($self) = @_;
523 46         121 return $self->{type};
524             }
525              
526              
527              
528             sub id {
529 25     25 1 29 my ($self) = @_;
530 25         78 return $self->{id};
531             }
532              
533              
534             sub uuid {
535 7832     7832 1 14378 my ($self, %opts) = @_;
536              
537 7832 50 100     19962 return $self->{id_cache}{WK_UUID()} if !$opts{no_defaults} && defined($self->{id_cache}) && defined($self->{id_cache}{WK_UUID()});
      66        
538              
539 7832 100       9559 if ($self->{type} == $well_known_uuid) {
540 7805         17035 return $self->{id};
541             }
542              
543 27 100       41 unless ($opts{no_defaults}) {
544             # Try to generate a UUID and recheck cache:
545 3         7 $self->_generate;
546 3 50 33     28 return $self->{id_cache}{WK_UUID()} if defined($self->{id_cache}) && defined($self->{id_cache}{WK_UUID()});
547             }
548              
549 24 50       126 return $opts{default} if exists $opts{default};
550 0         0 croak 'Identifier has no valid UUID';
551             }
552              
553             sub oid {
554 980     980 1 1146 my ($self, %opts) = @_;
555 980         898 my $type = $well_known{oid};
556              
557 980 100 66     3180 return $self->{id_cache}{WK_OID()} if !$opts{no_defaults} && defined($self->{id_cache}) && defined($self->{id_cache}{WK_OID()});
      100        
558              
559 492 50       691 if ($self->{type} == $type) {
560 0         0 return $self->{id};
561             }
562              
563 492 50       708 unless ($opts{no_defaults}) {
564 492 50       530 if (defined(my $uuid = $self->uuid(default => undef))) {
565 492         1752 return $self->{id_cache}{WK_OID()} = sprintf('2.25.%s', Math::BigInt->new('0x'.$uuid =~ tr/-//dr));
566             }
567             }
568              
569 0 0       0 return $opts{default} if exists $opts{default};
570 0         0 croak 'Identifier has no valid OID';
571             }
572              
573             sub uri {
574 981     981 1 1343 my ($self, %opts) = @_;
575 981         994 my $type = $well_known{uri};
576              
577 981 100 66     3810 if (!$opts{no_defaults} && !defined($opts{style}) && defined($self->{id_cache}) && defined($self->{id_cache}{WK_URI()})) {
      100        
      100        
578 488         1077 return $self->{id_cache}{WK_URI()};
579             }
580              
581 493 50       722 if ($self->{type} == $type) {
582 0         0 return $self->{id};
583             }
584              
585 493   100     1379 $opts{style} //= 'urn';
586              
587 493 50       650 unless ($opts{no_defaults}) {
588 493 50 66     1279 if ($self->{type} == $well_known{wd}) {
    50 33        
    100 33        
    50          
589 0         0 return $self->{id_cache}{WK_URI()} = sprintf('http://www.wikidata.org/entity/%s', $self->{id});
590             } elsif ($self->{type} == $well_known{doi}) {
591 0         0 return $self->{id_cache}{WK_URI()} = sprintf('https://doi.org/%s', $self->{id});
592             } elsif (defined(my $uuid = $self->uuid(default => undef)) && $opts{style} eq 'urn') {
593 492         1423 return $self->{id_cache}{WK_URI()} = sprintf('urn:uuid:%s', $uuid);
594             } elsif ($enabled_oid && defined(my $oid = $self->oid(default => undef)) && $opts{style} eq 'urn') {
595 0         0 return $self->{id_cache}{WK_URI()} = sprintf('urn:oid:%s', $oid);
596             } else {
597 1         523 my $u = URI->new("https://uriid.org/");
598 1         7738 my $type_uuid = $self->{type}->uuid;
599 1   33     9 $u->path_segments('', $uuid_to_uriid_org{$type_uuid} // $type_uuid, $self->{id});
600 1         130 return $self->{id_cache}{WK_URI()} = $u;
601             }
602             }
603              
604 0 0       0 return $opts{default} if exists $opts{default};
605 0         0 croak 'Identifier has no valid URI';
606             }
607              
608             sub sid {
609 983     983 1 1150 my ($self, %opts) = @_;
610 983         935 my $type = $well_known{sid};
611 983 100 66     2655 return $self->{id_cache}{WK_SID()} if defined($self->{id_cache}) && defined($self->{id_cache}{WK_SID()});
612 358 50       465 if ($self->{type} == $type) {
613 0         0 return $self->{id};
614             }
615              
616 358 50       960 return $opts{default} if exists $opts{default};
617 0         0 croak 'Identifier has no valid SID';
618             }
619              
620              
621              
622             sub ise {
623 22     22 1 47 my ($self, %opts) = @_;
624 22         30 my $type = $self->{type};
625 22         28 my $have_default = exists $opts{default};
626 22         25 my $default = delete $opts{default};
627 22         17 my $value;
628              
629 22 50 33     73 if ($type == $well_known{uuid} || $type == $well_known{oid} || $type == $well_known{uri}) {
      33        
630 22         27 $value = $self->{id};
631             } else {
632 0         0 $opts{default} = undef;
633 0   0     0 $value = $self->uuid(%opts) // $self->oid(%opts) // $self->uri(%opts);
      0        
634             }
635              
636 22 50       83 return $value if defined $value;
637 0 0       0 return $default if $have_default;
638 0         0 croak 'Identifier has no valid ISE';
639             }
640              
641              
642             sub as {
643 128     128 1 192 my ($self, $as, %opts) = @_;
644              
645 128 100 66     279 $as = $opts{rawtype} if $as eq 'raw' && defined($opts{rawtype});
646              
647 128 100 66     183 if (ref($as) && eval {$as->isa(__PACKAGE__)}) {
  6         20  
648 6         11 my $type_uuid = $as->uuid;
649 6         32 my $next_type;
650              
651 6 50       14 return $self->id if $self->type->eq($as);
652              
653 6         10 foreach my $test (qw(uuid oid uri sid)) {
654 24 100       48 if ($as == $well_known{$test}) {
655 3         5 $next_type = $test;
656 3         6 last;
657             }
658             }
659              
660 6 100       10 if (defined $next_type) {
661 3 0 33     10 return $self->{id_cache}{$type_uuid} if !$opts{no_defaults} && defined($self->{id_cache}) && defined($self->{id_cache}{$type_uuid});
      33        
662 3         6 $as = $next_type;
663             } else {
664 3 50 33     16 return $self->{id_cache}{$type_uuid} if defined($self->{id_cache}) && defined($self->{id_cache}{$type_uuid});
665 0 0       0 return $opts{default} if exists $opts{default};
666 0         0 croak 'Unknown/Unsupported as: '.$as;
667             }
668             }
669              
670 125 100 66     209 return $self if ($as =~ /^[A-Z]/ || $as =~ /::/) && eval {$self->isa($as)};
  122   66     359  
671              
672 3 50       16 if ($self->isa('Data::Identifier::Interface::Subobjects')) {
673 0         0 require Data::Identifier::Interface::Subobjects; # Is this required?
674 0   0     0 $opts{$_} //= $self->so_get($_, default => undef) foreach Data::Identifier::Interface::Subobjects->KEYS;
675             }
676              
677 3 50       10 if (defined(my $so = $opts{so})) {
678 0         0 require Data::Identifier::Interface::Subobjects; # Is this required?
679 0   0     0 $opts{$_} //= $so->so_get($_, default => undef) foreach Data::Identifier::Interface::Subobjects->KEYS;
680             }
681              
682 3 50       4 $self = __PACKAGE__->new(from => $self) unless eval {$self->isa(__PACKAGE__)};
  3         11  
683              
684 3 50 33     24 if ($as eq 'uuid' || $as eq 'oid' || $as eq 'uri' || $as eq 'sid' || $as eq 'ise') {
    0 33        
    0 33        
    0 33        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
685 3         10 my $func = $self->can($as);
686 3         9 return $self->$func(%opts);
687             } elsif ($as eq __PACKAGE__) {
688 0         0 return $self;
689             } elsif ($as eq 'URI') {
690 0         0 my $had_default = exists $opts{default};
691 0         0 my $default = delete $opts{default};
692 0         0 my $val = $self->uri(%opts, default => undef);
693              
694 0 0       0 return URI->new($val) if defined $val;
695 0 0       0 if ($had_default) {
696 0 0       0 return $default if ref $default;
697 0         0 return URI->new($default);
698             }
699 0         0 croak 'No value for URI';
700             } elsif ($as eq 'Mojo::URL') {
701 0         0 my $had_default = exists $opts{default};
702 0         0 my $default = delete $opts{default};
703 0         0 my $val = $self->uri(%opts, default => undef);
704              
705 0         0 require Mojo::URL;
706              
707 0 0       0 return Mojo::URL->new($val) if defined $val;
708 0 0       0 if ($had_default) {
709 0 0       0 return $default if ref $default;
710 0         0 return Mojo::URL->new($default);
711             }
712 0         0 croak 'No value for URI';
713             } elsif ($as eq 'Data::URIID::Result' && defined($opts{extractor})) {
714 0         0 return $opts{extractor}->lookup($self->type->uuid => $self->id);
715             } elsif ($as eq 'Data::URIID::Service' && defined($opts{extractor})) {
716 0         0 return $opts{extractor}->service($self->uuid);
717             } elsif ($as eq 'SIRTX::Datecode' && eval {
718 0         0 require SIRTX::Datecode;
719 0         0 SIRTX::Datecode->VERSION(v0.03);
720 0         0 1;
721             }) {
722 0         0 return SIRTX::Datecode->new(from => $self);
723             } elsif ($as eq 'Data::URIID::Colour' && eval {
724 0         0 require Data::URIID;
725 0         0 require Data::URIID::Colour;
726 0         0 Data::URIID::Colour->VERSION(v0.14);
727 0         0 1;
728             }) {
729 0         0 return Data::URIID::Colour->new(from => $self, %opts{qw(extractor db fii store)});
730             } elsif ($as eq 'Data::TagDB::Tag' && defined($opts{db})) {
731 0 0       0 if ($opts{autocreate}) {
732 0         0 return $opts{db}->create_tag($self);
733             } else {
734 0         0 return $opts{db}->tag_by_id($self);
735             }
736             } elsif ($as eq 'File::FStore::File' && defined($opts{store})) {
737 0         0 return scalar($opts{store}->query(ise => $self));
738             } elsif ($as eq 'Business::ISBN' && $self->type->eq('gtin')) {
739 0         0 require Business::ISBN;
740 0         0 my $val = Business::ISBN->new($self->id);
741 0 0 0     0 return $val if defined($val) && $val->is_valid;
742             }
743              
744 0 0       0 return $opts{default} if exists $opts{default};
745 0         0 croak 'Unknown/Unsupported as: '.$as;
746             }
747              
748              
749             sub eq {
750 41     41 1 63 my ($self, $other) = @_;
751              
752 41         52 foreach my $e ($self, $other) {
753 82 100 66     111 if (defined($e) && !scalar(eval {$e->isa(__PACKAGE__)})) {
  82         247  
754 4 50       7 if (defined $well_known{$e}) {
755 0         0 $e = $well_known{$e}
756             } else {
757 4         18 $e = Data::Identifier->new(from => $e);
758             }
759             }
760             }
761              
762 41 50       48 if (defined($self)) {
763 41 50       77 return undef unless defined $other;
764 41 100       133 return 1 if $self == $other;
765 11 50       17 return undef unless $self->type->eq($other->type);
766 11         18 return $self->id eq $other->id;
767             } else {
768 0         0 return !defined($other);
769             }
770             }
771              
772              
773             sub cmp {
774 0     0 1 0 my ($self, $other) = @_;
775              
776 0         0 foreach my $e ($self, $other) {
777 0 0 0     0 if (defined($e) && !scalar(eval {$e->isa(__PACKAGE__)})) {
  0         0  
778 0 0       0 if (defined $well_known{$e}) {
779 0         0 $e = $well_known{$e}
780             } else {
781 0         0 $e = Data::Identifier->new(from => $e);
782             }
783             }
784             }
785              
786 0 0       0 if (defined($self)) {
787 0 0       0 return undef unless defined $other;
788 0 0       0 return 0 if $self == $other;
789 0 0       0 if ((my $r = $self->type->cmp($other->type)) != 0) {
790 0         0 return $r;
791             }
792              
793             {
794 0         0 my $self_id = $self->id;
  0         0  
795 0         0 my $other_id = $other->id;
796              
797 0 0 0     0 if ((my ($sa, $sb) = $self_id =~ /^([^0-9]*)([0-9]+)$/) && (my ($oa, $ob) = $other_id =~ /^([^0-9]*)([0-9]+)$/)) {
798 0         0 my $r = $sa cmp $oa;
799 0 0       0 return $r if $r;
800 0         0 return $sb <=> $ob;
801             }
802              
803 0         0 return $self_id cmp $other_id;
804             }
805             } else {
806 0         0 return !defined($other);
807             }
808             }
809              
810              
811             #@returns __PACKAGE__
812             sub namespace {
813 3     3 1 6 my ($self, %opts) = @_;
814 3         4 my $has_default = exists $opts{default};
815 3         3 my $default = delete $opts{default};
816              
817 3         3 delete $opts{no_defaults};
818              
819 3 50       6 croak 'Stray options passed' if scalar keys %opts;
820              
821 3 50       10 return $self->{namespace} if defined $self->{namespace};
822              
823 0 0       0 return $default if $has_default;
824              
825 0         0 croak 'No namespace';
826             }
827              
828              
829             #@returns __PACKAGE__
830             sub generator {
831 0     0 1 0 my ($self, %opts) = @_;
832 0         0 my $has_default = exists $opts{default};
833 0         0 my $default = delete $opts{default};
834              
835 0         0 delete $opts{no_defaults};
836              
837 0 0       0 croak 'Stray options passed' if scalar keys %opts;
838              
839 0 0       0 return $self->{generator} if defined $self->{generator};
840              
841 0 0       0 return $default if $has_default;
842              
843 0         0 croak 'No generator';
844             }
845              
846              
847             sub request {
848 0     0 1 0 my ($self, %opts) = @_;
849 0         0 my $has_default = exists $opts{default};
850 0         0 my $default = delete $opts{default};
851              
852 0         0 delete $opts{no_defaults};
853              
854 0 0       0 croak 'Stray options passed' if scalar keys %opts;
855              
856 0 0       0 return $self->{request} if defined $self->{request};
857              
858 0 0       0 return $default if $has_default;
859              
860 0         0 croak 'No request';
861             }
862              
863              
864             #@returns __PACKAGE__
865             sub register {
866 973     973 1 1013 my ($self) = @_;
867 973         1274 $registered{$self->{type}->uuid}{$self->{id}} = $self;
868              
869 973         1110 foreach my $type_name (qw(uuid oid uri sid)) {
870 3892   50     6727 my $f = $self->can($type_name) || next;
871 3892   100     4094 my $v = $self->$f(default => undef) // next;
872 3534         246665 $registered{$well_known{$type_name}->uuid}{$v} = $self;
873             }
874              
875 973         975 foreach my $extra (WK_SNI()) {
876 973   100     1450 my $v = $self->{id_cache}{$extra} // next;
877 154         287 $registered{$extra}{$v} = $self;
878             }
879              
880 973         1389 return $self;
881             }
882              
883              
884              
885             sub displayname {
886 13     13 1 22 my ($self, %opts) = @_;
887              
888 13 50       35 if (defined(my $displayname = $self->{displayname})) {
889 13 50       26 $displayname = $self->$displayname() if ref $displayname;
890              
891             # recheck and return as any of the above conversions could result in $displayname becoming invalid.
892 13 50 33     72 return $displayname if defined($displayname) && length($displayname);
893             }
894              
895 0 0       0 if (defined(my $tagname = $self->tagname(default => undef, no_defaults => 1))) {
896 0         0 return $tagname;
897             }
898              
899 0 0       0 return $self->id.'' unless $opts{no_defaults}; # force stringification.
900 0 0       0 return $opts{default} if exists $opts{default};
901 0         0 croak 'No value for displayname';
902             }
903              
904              
905 0     0 1 0 sub displaycolour { my ($self, %opts) = @_; return $opts{default}; }
  0         0  
906 0     0 1 0 sub icontext { my ($self, %opts) = @_; return $opts{default}; }
  0         0  
907 0     0 1 0 sub description { my ($self, %opts) = @_; return $opts{default}; }
  0         0  
908              
909              
910             sub tagname {
911 0     0 1 0 my ($self, %opts) = @_;
912 0         0 my $had_default = exists $opts{default};
913 0         0 my $default = delete $opts{default};
914 0         0 my $list = delete $opts{list};
915              
916 0         0 delete $opts{no_defaults}; # for compatibility.
917              
918 0 0       0 croak 'Stray options passed' if scalar keys %opts;
919              
920 0 0       0 if (defined(my $tagname = $self->{tagname})) {
921 0 0       0 return @{$tagname} if $list;
  0         0  
922              
923 0         0 return $tagname->[0];
924             }
925              
926 0 0       0 if ($had_default) {
927 0 0       0 return @{$default} if $list;
  0         0  
928 0         0 return $default;
929             }
930 0         0 croak 'No value for tagname found';
931             }
932              
933             # ---- Private helpers ----
934              
935             sub import {
936 15     15   1058 my ($pkg, $opts) = @_;
937 15 50       2682 return unless defined $opts;
938 0 0       0 croak 'Bad options' unless ref($opts) eq 'HASH';
939              
940 0 0       0 if (defined(my $disable = $opts->{disable})) {
941 0 0       0 $disable = [split /\s*,\s*/, $disable] unless ref $disable;
942 0         0 foreach my $to_disable (@{$disable}) {
  0         0  
943 0 0       0 if ($to_disable eq 'oid') {
944 0         0 $enabled_oid = undef;
945 0         0 undef *oid;
946             } else {
947 0         0 croak 'Unknown feature: '.$to_disable;
948             }
949             }
950             }
951             }
952              
953             sub _generate {
954 3     3   5 my ($self) = @_;
955 3 50       5 unless (exists $self->{_generate}) {
956 3         6 my __PACKAGE__ $type = $self->type;
957              
958 3 50       5 if (defined(my $generate = $type->{generate})) {
959 3 50       4 unless (ref $generate) {
960 3         10 $self->{generate} = $generate = {style => $generate};
961             }
962              
963 3   50     10 $self->{id_cache} //= {};
964              
965 3 50       2 if (defined(my __PACKAGE__ $ns = eval {$type->namespace->uuid})) {
  3         65  
966 3         3 my $style = $generate->{style};
967 3         3 my $input;
968              
969 3 50       4 if ($style eq 'id-based') {
970 3         4 $input = lc($self->id);
971             } else {
972 0         0 croak 'Unsupported generator style';
973             }
974              
975 3 50       5 if (defined $input) {
976 3         12 require Data::Identifier::Generate;
977 3         12 $self->{id_cache}{WK_UUID()} = Data::Identifier::Generate->_uuid_v5($ns, $input);
978             }
979             }
980             }
981             }
982 3         6 $self->{_generate} = undef;
983             }
984              
985             sub _known_provider {
986 9     9   31 my ($pkg, $class, %opts) = @_;
987 9 50       24 croak 'Unsupported options passed' if scalar(keys %opts);
988              
989 9 100 33     64 if ($class eq 'wellknown') {
    50          
990 8         15 state $wellknown = do {
991 7         37 my %hash = map{$_ => $_} values(%well_known), map {values %{$_}} values(%registered);
  1708         2313  
  35         63  
  35         454  
992 7         230 [values %hash];
993             };
994              
995 8         23 return ($wellknown, rawtype => __PACKAGE__);
996             } elsif ($class eq 'registered' || $class eq ':all') {
997 1         4 my %hash = map{$_ => $_} values(%well_known), map {values %{$_}} values(%registered);
  244         369  
  5         6  
  5         55  
998 1         38 return ([values %hash], rawtype => __PACKAGE__);
999             }
1000              
1001 0           croak 'Unsupported class';
1002             }
1003              
1004             1;
1005              
1006             __END__