line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package UR::Object::Property; |
2
|
396
|
|
|
396
|
|
22544
|
use warnings; |
|
267
|
|
|
|
|
3155
|
|
|
267
|
|
|
|
|
7724
|
|
3
|
318
|
|
|
318
|
|
9094
|
use strict; |
|
266
|
|
|
|
|
401
|
|
|
266
|
|
|
|
|
6355
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
require UR; |
6
|
|
|
|
|
|
|
|
7
|
301
|
|
|
301
|
|
187606
|
use Lingua::EN::Inflect; |
|
266
|
|
|
|
|
4921683
|
|
|
266
|
|
|
|
|
23419
|
|
8
|
298
|
|
|
298
|
|
7280
|
use Class::AutoloadCAN; |
|
266
|
|
|
|
|
429
|
|
|
266
|
|
|
|
|
3150
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION;; |
11
|
|
|
|
|
|
|
our @CARP_NOT = qw( UR::DataSource::RDBMS UR::Object::Type ); |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# class_meta and r_class_meta duplicate the functionality if two properties of the same name, |
14
|
|
|
|
|
|
|
# but these are faster |
15
|
|
|
|
|
|
|
sub class_meta { |
16
|
1959
|
|
|
1959
|
1
|
41449
|
return shift->{'class_name'}->class->__meta__; |
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub r_class_meta { |
20
|
856
|
|
|
874
|
0
|
5943
|
return shift->{'data_type'}->class->__meta__; |
21
|
|
|
|
|
|
|
} |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub is_direct { |
25
|
0
|
|
|
18
|
0
|
0
|
my $self = shift; |
26
|
18
|
0
|
0
|
|
|
2880
|
if ($self->is_calculated or $self->is_constant or $self->is_many or $self->via) { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
27
|
0
|
|
|
|
|
0
|
return 0; |
28
|
|
|
|
|
|
|
} |
29
|
0
|
|
|
|
|
0
|
return 1; |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub is_numeric { |
33
|
2296
|
|
|
2295
|
0
|
6172
|
my $self = shift; |
34
|
2278
|
100
|
|
|
|
5997
|
unless (defined($self->{'_is_numeric'})) { |
35
|
1238
|
|
|
|
|
3610
|
my $class = $self->_data_type_as_class_name; |
36
|
1255
|
50
|
|
|
|
6056
|
unless ($class) { |
37
|
0
|
|
|
|
|
0
|
return; |
38
|
|
|
|
|
|
|
} |
39
|
1238
|
|
|
|
|
10019
|
$self->{'_is_numeric'} = $class->isa("UR::Value::Number"); |
40
|
|
|
|
|
|
|
} |
41
|
2295
|
|
|
|
|
23837
|
return $self->{'_is_numeric'}; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub is_text { |
45
|
3
|
|
|
20
|
0
|
5
|
my $self = shift; |
46
|
3
|
100
|
|
|
|
10
|
unless (defined($self->{'_is_text'})) { |
47
|
18
|
|
|
|
|
3343
|
my $class = $self->_data_type_as_class_name; |
48
|
1
|
50
|
|
|
|
3
|
unless ($class) { |
49
|
0
|
|
|
|
|
0
|
return; |
50
|
|
|
|
|
|
|
} |
51
|
1
|
|
|
|
|
7
|
$self->{'_is_text'} = $class->isa("UR::Value::Text"); |
52
|
|
|
|
|
|
|
} |
53
|
3
|
|
|
|
|
7
|
return $self->{'_is_text'}; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub is_valid_storage_for_value { |
57
|
116
|
|
|
133
|
0
|
175
|
my($self, $value) = @_; |
58
|
|
|
|
|
|
|
|
59
|
116
|
|
|
|
|
313
|
my $data_class_name = $self->_data_type_as_class_name; |
60
|
116
|
100
|
|
|
|
1489
|
return 1 if ($value->isa($data_class_name)); |
61
|
|
|
|
|
|
|
|
62
|
40
|
100
|
|
|
|
475
|
if ($data_class_name->isa('UR::Value') ) { |
63
|
39
|
|
|
|
|
199
|
my @underlying_types = $data_class_name->underlying_data_types; |
64
|
39
|
|
|
|
|
90
|
foreach my $underlying_type ( @underlying_types ) { |
65
|
8
|
50
|
|
|
|
17
|
return 1 if ($value->isa($underlying_type)); |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
} |
68
|
32
|
|
|
|
|
195
|
return 0; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub alias_for { |
72
|
6595
|
|
|
6595
|
0
|
7062
|
my $self = shift; |
73
|
|
|
|
|
|
|
|
74
|
6595
|
100
|
66
|
|
|
20009
|
if ($self->{'via'} and $self->{'to'} and $self->{'via'} eq '__self__') { |
|
|
|
66
|
|
|
|
|
75
|
4
|
|
|
|
|
14
|
return $self->{'to'}; |
76
|
|
|
|
|
|
|
} else { |
77
|
6591
|
|
|
|
|
19912
|
return $self->{'property_name'}; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub _convert_data_type_for_source_class_to_final_class { |
82
|
2712
|
|
|
2712
|
|
4091
|
my ($class, $foreign_class, $source_class) = @_; |
83
|
|
|
|
|
|
|
|
84
|
2712
|
|
100
|
|
|
5816
|
$foreign_class ||= ''; |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# TODO: allowing "is => 'Text'" instead of is => 'UR::Value::Text' is syntactic sugar |
87
|
|
|
|
|
|
|
# We should have an is_primitive flag set on these so we do efficient work. |
88
|
|
|
|
|
|
|
|
89
|
2712
|
|
|
|
|
13304
|
my ($ns) = ($source_class =~ /^([^:]+)::/); |
90
|
2712
|
100
|
100
|
|
|
19307
|
if ($ns and not $ns->isa("UR::Namespace")) { |
91
|
97
|
|
|
|
|
1170
|
$ns = undef; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
2712
|
|
|
|
|
3043
|
my $final_class; |
95
|
2712
|
100
|
|
|
|
5403
|
if ($foreign_class) { |
96
|
2414
|
100
|
|
|
|
14697
|
if ($foreign_class->can('__meta__')) { |
97
|
206
|
|
|
|
|
1668
|
$final_class = $foreign_class; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
else { |
100
|
2208
|
|
|
|
|
134941
|
my ($ns_value_class, $ur_value_class); |
101
|
|
|
|
|
|
|
|
102
|
2208
|
100
|
66
|
|
|
9488
|
if ($ns and $ns->can("get")) { |
103
|
2072
|
|
|
|
|
15573
|
$ns_value_class = $ns . '::Value::' . $foreign_class; |
104
|
2072
|
100
|
|
|
|
8433
|
if ($ns_value_class->can('__meta__')) { |
105
|
665
|
|
|
|
|
46008
|
$final_class = $ns_value_class; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
2208
|
100
|
|
|
|
79771
|
if (!$final_class) { |
110
|
1543
|
|
|
|
|
2661
|
$ur_value_class = 'UR::Value::' . $foreign_class; |
111
|
1543
|
100
|
|
|
|
6514
|
if ($ur_value_class->can('__meta__')) { |
112
|
428
|
|
|
|
|
19994
|
$final_class = $ur_value_class; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
} |
115
|
2208
|
100
|
|
|
|
35889
|
if (!$final_class) { |
116
|
1115
|
|
|
|
|
3003
|
$ur_value_class = 'UR::Value::' . ucfirst(lc($foreign_class)); |
117
|
1115
|
100
|
|
|
|
3470
|
if ($ur_value_class->can('__meta__')) { |
118
|
150
|
|
|
|
|
4428
|
$final_class = $ur_value_class; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
2712
|
100
|
|
|
|
32302
|
if (!$final_class) { |
125
|
1263
|
100
|
|
|
|
6518
|
if (Class::Autouse->class_exists($foreign_class)) { |
|
|
50
|
|
|
|
|
|
126
|
10
|
|
|
|
|
176
|
return $foreign_class; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
elsif ($foreign_class =~ /::/) { |
129
|
0
|
|
|
|
|
0
|
return $foreign_class; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
else { |
132
|
298
|
|
|
298
|
|
50905
|
eval "use $foreign_class;"; |
|
0
|
|
|
297
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
297
|
|
|
|
|
47028
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
1253
|
|
|
|
|
270684
|
|
133
|
1253
|
100
|
|
|
|
4190
|
if (!$@) { |
134
|
1
|
|
|
|
|
6
|
return $foreign_class; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
1252
|
50
|
66
|
|
|
27475
|
if (!$ns or $ns->get()->allow_sloppy_primitives) { |
138
|
|
|
|
|
|
|
# no colons, and no namespace: no choice but to assume it's a sloppy primitive |
139
|
1252
|
|
|
|
|
6728
|
return 'UR::Value::SloppyPrimitive'; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
else { |
142
|
0
|
|
|
|
|
0
|
Carp::confess("Failed to find a ${ns}::Value::* or UR::Value::* module for primitive type $foreign_class!"); |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
1449
|
|
|
|
|
8683
|
return $final_class; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub _data_type_as_class_name { |
151
|
4287
|
|
|
4287
|
|
6186
|
my $self = $_[0]; |
152
|
4287
|
|
66
|
|
|
17125
|
return $self->{_data_type_as_class_name} ||= do { |
153
|
2609
|
|
|
|
|
6879
|
my $source_class = $self->class_name; |
154
|
|
|
|
|
|
|
#this is so NUMBER -> Number |
155
|
2609
|
|
|
|
|
6456
|
my $foreign_class = $self->data_type; |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
|
158
|
2609
|
100
|
|
|
|
5569
|
if (not $foreign_class) { |
159
|
235
|
100
|
66
|
|
|
787
|
if ($self->via or $self->to) { |
160
|
4
|
|
|
|
|
18
|
my @joins = UR::Object::Join->resolve_chain( |
161
|
|
|
|
|
|
|
$self->class_name, |
162
|
|
|
|
|
|
|
$self->property_name, |
163
|
|
|
|
|
|
|
$self->property_name, |
164
|
|
|
|
|
|
|
); |
165
|
4
|
|
|
|
|
20
|
$foreign_class = $joins[-1]->foreign_class; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
2609
|
|
|
|
|
7364
|
__PACKAGE__->_convert_data_type_for_source_class_to_final_class($foreign_class, $source_class); |
170
|
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# TODO: this is a method on the data source which takes a given property. |
174
|
|
|
|
|
|
|
# Returns the table and column for this property. |
175
|
|
|
|
|
|
|
# If this particular property doesn't have a column_name, and it |
176
|
|
|
|
|
|
|
# overrides a property defined on a parent class, then walk up the |
177
|
|
|
|
|
|
|
# inheritance and find the right one |
178
|
|
|
|
|
|
|
sub table_and_column_name_for_property { |
179
|
320
|
|
|
320
|
0
|
357
|
my $self = shift; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# Shortcut - this property has a column_name, so the class should have the right |
182
|
|
|
|
|
|
|
# table_name |
183
|
320
|
100
|
|
|
|
705
|
if ($self->column_name) { |
184
|
317
|
|
|
|
|
684
|
return ($self->class_name->__meta__->table_name, $self->column_name); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
3
|
|
|
|
|
24
|
my $property_name = $self->property_name; |
188
|
3
|
|
|
|
|
13
|
my @class_metas = $self->class_meta->parent_class_metas; |
189
|
|
|
|
|
|
|
|
190
|
3
|
|
|
|
|
5
|
my %seen; |
191
|
3
|
|
|
|
|
11
|
while (@class_metas) { |
192
|
3
|
|
|
|
|
7
|
my $class_meta = shift @class_metas; |
193
|
3
|
50
|
|
|
|
17
|
next if ($seen{$class_meta}++); |
194
|
|
|
|
|
|
|
|
195
|
3
|
|
|
|
|
18
|
my $p = $class_meta->property_meta_for_name($property_name); |
196
|
3
|
100
|
|
|
|
12
|
next unless $p; |
197
|
|
|
|
|
|
|
|
198
|
2
|
50
|
33
|
|
|
9
|
if ($p->column_name && $class_meta->table_name) { |
199
|
2
|
|
|
|
|
10
|
return ($class_meta->table_name, $p->column_name); |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
0
|
|
|
|
|
0
|
push @class_metas, $class_meta->parent_class_metas; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# This property has no column anywhere in the class' inheritance |
206
|
1
|
|
|
|
|
4
|
return; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Return true if resolution of this property involves an ID property of |
211
|
|
|
|
|
|
|
# any class. |
212
|
|
|
|
|
|
|
sub _involves_id_property { |
213
|
46
|
|
|
46
|
|
41
|
my $self = shift; |
214
|
|
|
|
|
|
|
|
215
|
46
|
|
|
|
|
92
|
my $is_id = $self->is_id; |
216
|
46
|
100
|
|
|
|
157
|
return 1 if defined($is_id); |
217
|
|
|
|
|
|
|
|
218
|
33
|
100
|
|
|
|
65
|
if ($self->id_by) { |
219
|
5
|
|
|
|
|
17
|
my $class_meta = $self->class_meta; |
220
|
5
|
|
|
|
|
16
|
my $id_by_list = $self->id_by; |
221
|
5
|
|
|
|
|
13
|
foreach my $id_by ( @$id_by_list ) { |
222
|
5
|
|
|
|
|
14
|
my $id_by_meta = $class_meta->property_meta_for_name($id_by); |
223
|
5
|
50
|
33
|
|
|
26
|
return 1 if ($id_by_meta and $id_by_meta->_involves_id_property); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
|
227
|
28
|
100
|
|
|
|
60
|
if ($self->via) { |
228
|
13
|
|
|
|
|
38
|
my $via_meta = $self->via_property_meta; |
229
|
13
|
50
|
33
|
|
|
139
|
return 1 if ($via_meta and $via_meta ne $self and $via_meta->_involves_id_property); |
|
|
|
33
|
|
|
|
|
230
|
|
|
|
|
|
|
|
231
|
13
|
50
|
|
|
|
39
|
if ($self->to) { |
232
|
13
|
|
|
|
|
41
|
my $to_meta = $self->to_property_meta; |
233
|
13
|
100
|
66
|
|
|
56
|
return 1 if ($to_meta and $to_meta->_involves_id_property); |
234
|
|
|
|
|
|
|
|
235
|
2
|
50
|
|
|
|
7
|
if ($self->where) { |
236
|
2
|
50
|
|
|
|
19
|
unless ($to_meta) { |
237
|
0
|
|
|
|
|
0
|
Carp::confess("Property '" . $self->property_name . "' of class " . $self->class_name |
238
|
|
|
|
|
|
|
. " has 'to' metadata that does not resolve to a known property."); |
239
|
|
|
|
|
|
|
} |
240
|
2
|
|
|
|
|
6
|
my $other_class_meta = $to_meta->class_meta; |
241
|
2
|
|
|
|
|
7
|
my $where = $self->where; |
242
|
2
|
|
|
|
|
9
|
for (my $i = 0; $i < @$where; $i += 2) { |
243
|
2
|
|
|
|
|
6
|
my $where_meta = $other_class_meta->property_meta_for_name($where->[$i]); |
244
|
2
|
50
|
33
|
|
|
9
|
return 1 if ($where_meta and $where_meta->_involves_id_property); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
} |
249
|
15
|
|
|
|
|
54
|
return 0; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# For via/to delegated properties, return the property meta in the same |
254
|
|
|
|
|
|
|
# class this property delegates through |
255
|
|
|
|
|
|
|
sub via_property_meta { |
256
|
882
|
|
|
882
|
1
|
1267
|
my $self = shift; |
257
|
|
|
|
|
|
|
|
258
|
882
|
50
|
33
|
|
|
2089
|
return unless ($self->is_delegated and $self->via); |
259
|
882
|
|
|
|
|
10405
|
my $class_meta = $self->class_meta; |
260
|
882
|
|
|
|
|
53934
|
return $class_meta->property_meta_for_name($self->via); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub final_property_meta { |
264
|
161
|
|
|
161
|
0
|
233
|
my $self = shift; |
265
|
|
|
|
|
|
|
|
266
|
161
|
|
|
|
|
182
|
my $closure; |
267
|
|
|
|
|
|
|
$closure = sub { |
268
|
267
|
50
|
|
267
|
|
531
|
return unless defined $_[0]; |
269
|
267
|
100
|
100
|
|
|
901
|
if ($_[0]->is_delegated and $_[0]->via) { |
270
|
106
|
50
|
|
|
|
342
|
if ($_[0]->to) { |
271
|
106
|
|
|
|
|
561
|
return $closure->($_[0]->to_property_meta); |
272
|
|
|
|
|
|
|
} else { |
273
|
0
|
|
|
|
|
0
|
return $closure->($_[0]->via_property_meta); |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
} else { |
276
|
161
|
|
|
|
|
327
|
return $_[0]; |
277
|
|
|
|
|
|
|
} |
278
|
161
|
|
|
|
|
769
|
}; |
279
|
161
|
|
|
|
|
348
|
my $final = $closure->($self); |
280
|
|
|
|
|
|
|
|
281
|
161
|
100
|
66
|
|
|
746
|
return if !defined $final || $final->id eq $self->id; |
282
|
91
|
|
|
|
|
252
|
return $final; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# For via/to delegated properties, return the property meta on the foreign |
286
|
|
|
|
|
|
|
# class that this property delegates to |
287
|
|
|
|
|
|
|
sub to_property_meta { |
288
|
865
|
|
|
865
|
1
|
1779
|
my $self = shift; |
289
|
|
|
|
|
|
|
|
290
|
865
|
50
|
33
|
|
|
2490
|
return unless ($self->is_delegated && $self->to); |
291
|
|
|
|
|
|
|
|
292
|
865
|
|
|
|
|
3008
|
my $via_meta = $self->via_property_meta(); |
293
|
865
|
100
|
|
|
|
46295
|
return unless $via_meta; |
294
|
|
|
|
|
|
|
|
295
|
864
|
|
|
|
|
20801
|
my $remote_class = $via_meta->data_type; |
296
|
|
|
|
|
|
|
# unless ($remote_class) { |
297
|
|
|
|
|
|
|
# # Can we guess what the data type is for multiply indirect properties? |
298
|
|
|
|
|
|
|
# if ($via_meta->to) { |
299
|
|
|
|
|
|
|
# my $to_property_meta = $via_meta->to_property_meta; |
300
|
|
|
|
|
|
|
# $remote_class = $to_property_meta->data_type if ($to_property_meta); |
301
|
|
|
|
|
|
|
# } |
302
|
|
|
|
|
|
|
# } |
303
|
864
|
50
|
|
|
|
3996
|
return unless $remote_class; |
304
|
864
|
|
|
|
|
3596
|
my $remote_class_meta = UR::Object::Type->get($remote_class); |
305
|
864
|
100
|
|
|
|
2086
|
return unless $remote_class_meta; |
306
|
|
|
|
|
|
|
|
307
|
858
|
|
|
|
|
2349
|
return $remote_class_meta->property_meta_for_name($self->to); |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
sub get_property_name_pairs_for_join { |
312
|
8738
|
|
|
8738
|
0
|
10388
|
my ($self) = @_; |
313
|
8738
|
100
|
|
|
|
18709
|
unless ($self->{'_get_property_name_pairs_for_join'}) { |
314
|
875
|
|
|
|
|
2855
|
my @linkage = $self->_get_direct_join_linkage(); |
315
|
873
|
100
|
|
|
|
2186
|
unless (@linkage) { |
316
|
17
|
|
|
|
|
28
|
Carp::croak("Cannot resolve underlying property joins for property '" |
317
|
|
|
|
|
|
|
. $self->property_name . "' of class " |
318
|
|
|
|
|
|
|
. $self->class_name |
319
|
|
|
|
|
|
|
. ": Couldn't determine which properties link to the remote class"); |
320
|
|
|
|
|
|
|
} |
321
|
856
|
|
|
|
|
1076
|
my @results; |
322
|
856
|
100
|
|
|
|
2818
|
if ($self->reverse_as) { |
323
|
36
|
|
|
|
|
85
|
@results = map { [ $_->[1] => $_->[0] ] } @linkage; |
|
37
|
|
|
|
|
151
|
|
324
|
|
|
|
|
|
|
} else { |
325
|
820
|
|
|
|
|
1542
|
@results = map { [ $_->[0] => $_->[1] ] } @linkage; |
|
1256
|
|
|
|
|
3310
|
|
326
|
|
|
|
|
|
|
} |
327
|
856
|
|
|
|
|
2386
|
$self->{'_get_property_name_pairs_for_join'} = \@results; |
328
|
|
|
|
|
|
|
} |
329
|
8719
|
|
|
|
|
7949
|
return @{$self->{'_get_property_name_pairs_for_join'}}; |
|
8719
|
|
|
|
|
22104
|
|
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub _get_direct_join_linkage { |
333
|
912
|
|
|
912
|
|
1469
|
my ($self) = @_; |
334
|
912
|
|
|
|
|
1183
|
my @retval; |
335
|
912
|
100
|
|
|
|
2552
|
if (my $id_by = $self->id_by) { |
|
|
100
|
|
|
|
|
|
336
|
856
|
|
|
|
|
3148
|
my $r_class_meta = $self->r_class_meta; |
337
|
856
|
50
|
|
|
|
2363
|
unless ($r_class_meta) { |
338
|
0
|
|
|
|
|
0
|
Carp::croak("Property '" . $self->property_name . "' of class '" . $self->class_name . "' " |
339
|
|
|
|
|
|
|
. "has data_type '" . $self->data_type ."' with no class metadata"); |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
|
342
|
856
|
|
|
|
|
1107
|
my @my_id_by = @{ $self->id_by }; |
|
856
|
|
|
|
|
2138
|
|
343
|
856
|
|
|
|
|
1219
|
my @their_id_by = @{ $r_class_meta->{'id_by'} }; |
|
856
|
|
|
|
|
2565
|
|
344
|
856
|
100
|
100
|
|
|
5465
|
if (! @their_id_by |
|
|
|
66
|
|
|
|
|
345
|
|
|
|
|
|
|
or |
346
|
|
|
|
|
|
|
(@my_id_by == 1 and @their_id_by > 1) |
347
|
|
|
|
|
|
|
) { |
348
|
268
|
|
|
|
|
650
|
@their_id_by = ( 'id' ); |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
856
|
50
|
|
|
|
2319
|
unless (@my_id_by == @their_id_by) { |
352
|
0
|
|
|
|
|
0
|
Carp::croak("Property '" . $self->property_name . "' of class '" . $self->class_name . "' " |
353
|
|
|
|
|
|
|
. "has " . scalar(@my_id_by) . " id_by elements, while its data_type (" |
354
|
|
|
|
|
|
|
. $self->data_type .") has " . scalar(@their_id_by)); |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
856
|
|
|
|
|
2650
|
for (my $i = 0; $i < @my_id_by; $i++) { |
358
|
1293
|
|
|
|
|
4141
|
push @retval, [ $my_id_by[$i], $their_id_by[$i] ]; |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
elsif (my $reverse_as = $self->reverse_as) { |
363
|
39
|
|
|
|
|
102
|
my $r_class_name = $self->data_type; |
364
|
39
|
|
|
|
|
174
|
@retval = |
365
|
|
|
|
|
|
|
$r_class_name->__meta__->property_meta_for_name($reverse_as)->_get_direct_join_linkage(); |
366
|
|
|
|
|
|
|
} |
367
|
910
|
|
|
|
|
2019
|
return @retval; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
sub _resolve_join_chain { |
371
|
698
|
|
|
698
|
|
1147
|
my $self = shift; |
372
|
698
|
|
|
|
|
1827
|
return UR::Object::Join->resolve_chain( |
373
|
|
|
|
|
|
|
$self->class_name, |
374
|
|
|
|
|
|
|
$self->property_name, |
375
|
|
|
|
|
|
|
); |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
sub label_text { |
379
|
|
|
|
|
|
|
# The name of the property in friendly terms. |
380
|
0
|
|
|
0
|
0
|
0
|
my ($self,$obj) = @_; |
381
|
0
|
|
|
|
|
0
|
my $property_name = $self->property_name; |
382
|
0
|
|
|
|
|
0
|
my @words = App::Vocabulary->filter_vocabulary(map { ucfirst(lc($_)) } split(/\s+/,$property_name)); |
|
0
|
|
|
|
|
0
|
|
383
|
0
|
|
|
|
|
0
|
my $label = join(" ", @words); |
384
|
0
|
|
|
|
|
0
|
return $label; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# This gets around the need to make a custom property subclass |
388
|
|
|
|
|
|
|
# when a class has an attributes_have specification. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# This primary example of this in base infrastructure is that |
391
|
|
|
|
|
|
|
# all Commands have is_input, is_output and is_param attributes. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Note: it's too permissive and will make an accessor for any hash key. |
394
|
|
|
|
|
|
|
# The updated code should not do this. |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
sub CAN { |
397
|
18109
|
|
|
18109
|
0
|
439694
|
my ($thisclass, $method, $self) = @_; |
398
|
18109
|
100
|
|
|
|
29539
|
if (ref($self)) { |
399
|
4277
|
|
|
|
|
5546
|
my $accessor_key = '_' . $method . "_accessor"; |
400
|
4277
|
100
|
|
|
|
7695
|
if (my $method = $self->{$accessor_key}) { |
401
|
2960
|
|
|
|
|
4002
|
return $method; |
402
|
|
|
|
|
|
|
} |
403
|
1317
|
100
|
33
|
|
|
3542
|
if ($self->class_name->__meta__->{attributes_have} |
404
|
|
|
|
|
|
|
and |
405
|
|
|
|
|
|
|
exists $self->class_name->__meta__->{attributes_have}{$method} |
406
|
|
|
|
|
|
|
) { |
407
|
|
|
|
|
|
|
return $self->{$accessor_key} = sub { |
408
|
2549
|
|
|
2549
|
|
18297
|
return $_[0]->{$method}; |
409
|
|
|
|
|
|
|
} |
410
|
263
|
|
|
|
|
1134
|
} |
411
|
|
|
|
|
|
|
} |
412
|
14886
|
|
|
|
|
20842
|
return; |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
1; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=pod |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head1 NAME |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
UR::Object::Property - Class representing metadata about a class property |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=head1 SYNOPSIS |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
my $prop = UR::Object::Property->get(class_name => 'Some::Class', property_name => 'foo'); |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
my $class_meta = Some::Class->__meta__; |
429
|
|
|
|
|
|
|
my $prop2 = $class_meta->property_meta_for_name('foo'); |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# Print out the meta-property name and its value of $prop2 |
432
|
|
|
|
|
|
|
print map { " $_ : ".$prop2->$_ } |
433
|
|
|
|
|
|
|
qw(class_name property_name data_type default_value); |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
=head1 DESCRIPTION |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
Instances of this class represent properties of classes. For every item |
438
|
|
|
|
|
|
|
mentioned in the 'has' or 'id_by' section of a class definition become Property |
439
|
|
|
|
|
|
|
objects. |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=head1 INHERITANCE |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
UR::Object::Property is a subclass of L |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head1 PROPERTY TYPES |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
For this class definition: |
448
|
|
|
|
|
|
|
class Some::Class { |
449
|
|
|
|
|
|
|
has => [ |
450
|
|
|
|
|
|
|
other_id => { is => 'Text' }, |
451
|
|
|
|
|
|
|
other => { is => 'Some::Other', id_by => 'foo_id' }, |
452
|
|
|
|
|
|
|
bar => { via => 'other', to => 'bar' }, |
453
|
|
|
|
|
|
|
foos => { is => 'Some::Foo', reverse_as => 'some', is_many => 1 }, |
454
|
|
|
|
|
|
|
uc_other_id => { calculate_from => 'other_id', |
455
|
|
|
|
|
|
|
calculate_perl => 'uc($other_id)' }, |
456
|
|
|
|
|
|
|
], |
457
|
|
|
|
|
|
|
}; |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Properties generally fall in to one of these categories: |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=over 4 |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=item regular property |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
A regular property of a class holds a single scalar. In this case, |
466
|
|
|
|
|
|
|
'other_id' is a regular property. |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=item object accessor |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
An object accessor property returns objects of some class. The properties |
471
|
|
|
|
|
|
|
of this class must link in some way with all the ID properties of the remote |
472
|
|
|
|
|
|
|
class (the 'is' declaration). 'other' is an object accessor property. This |
473
|
|
|
|
|
|
|
is how one-to-one relationships are implemented. |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=item via property |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
When a class has some object accessor property, and it is helpful for an |
478
|
|
|
|
|
|
|
object to assumme the value of the remote class's properties, you can set |
479
|
|
|
|
|
|
|
up a 'via' property. In the example above, an object of this class |
480
|
|
|
|
|
|
|
gets the value of its 'bar' property via the 'other' object it's linked |
481
|
|
|
|
|
|
|
to, from that object's 'bar' property. |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=item reverse as or is many property |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
This is how one-to-many relationships are implemented. In this case, |
486
|
|
|
|
|
|
|
the Some::Foo class must have an object accessor property called 'some', |
487
|
|
|
|
|
|
|
and the 'foos' property will return a list of all the Some::Foo objects |
488
|
|
|
|
|
|
|
where their 'some' property would have returned that object. |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=item calculated property |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
A calculated property doesn't store its data directly in the object, but |
493
|
|
|
|
|
|
|
when its accessor is called, the calculation code is executed. |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
=back |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
=head1 PROPERTIES |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
Each property has a method of the same name |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=head2 Direct Properties |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=over 4 |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=item class_name => Text |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
The name of the class this Property is attached to |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=item property_name => Text |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
The name of the property. The pair of class_name and property name are |
512
|
|
|
|
|
|
|
the ID properties of UR::Object::Property |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=item column_name => Text |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
If the class is backed by a database table, then the column this property's |
517
|
|
|
|
|
|
|
data comes from is stored here |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=item data_type => Text |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
The type of data stored in this property. Corresponds to the 'is' part of |
522
|
|
|
|
|
|
|
a class's property definition. |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=item data_length => Number |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
The maximum size of data stored in this property |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=item default_value |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
For is_optional properties, the default value given when an object is created |
531
|
|
|
|
|
|
|
and this property is not assigned a value. |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=item valid_values => ARRAY |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
A listref of enumerated values this property may be set to |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=item doc => Text |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
A place for documentation about this property |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
=item is_id => Boolean |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
Indicates whether this is an ID property of the class |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=item is_optional => Boolean |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Indicates whether this is property may have the value undef when the object |
548
|
|
|
|
|
|
|
is created |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=item is_transient => Boolean |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Indicates whether this is property is transient? |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=item is_constant => Boolean |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
Indicates whether this property can be changed after the object is created. |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
=item is_mutable => Boolean |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
Indicates this property can be changed via its accessor. Properties cannot |
561
|
|
|
|
|
|
|
be both constant and mutable |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=item is_volatile => Boolean |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
Indicates this property can be changed by a mechanism other than its normal |
566
|
|
|
|
|
|
|
accessor method. Signals are not emitted even when it does change via |
567
|
|
|
|
|
|
|
its normal accessor method. |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=item is_classwide => Boolean |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
Indicates this property's storage is shared among all instances of the class. |
572
|
|
|
|
|
|
|
When the value is changed for one instance, that change is effective for all |
573
|
|
|
|
|
|
|
instances. |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
=item is_delegated => Boolean |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
Indicates that the value for this property is not stored in the object |
578
|
|
|
|
|
|
|
directly, but is delegated to another object or class. |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=item is_calculated => Boolean |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
Indicates that the value for this property is not a part of the object'd |
583
|
|
|
|
|
|
|
data directly, but is calculated in some way. |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=item is_transactional => Boolean |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
Indicates the changes to the value of this property is tracked by a Context's |
588
|
|
|
|
|
|
|
transaction and can be rolled back if necessary. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=item is_abstract => Boolean |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
Indicates this property exists in a base class, but must be overridden in |
593
|
|
|
|
|
|
|
a derived class. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=item is_concrete => Boolean |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
Antonym for is_abstract. Properties cannot be both is_abstract and is_concrete, |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=item is_final => Boolean |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
Indicates this property cannot be overridden in a derived class. |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
=item is_deprecated => Boolean |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
Indicates this property's use is deprecated. It has no effect in the use |
606
|
|
|
|
|
|
|
of the property in any way, but is useful in documentation. |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=item implied_by => Text |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
If this property is created as a result of another property's existence, |
611
|
|
|
|
|
|
|
implied_by is the name of that other property. This can happen in the |
612
|
|
|
|
|
|
|
case where an object accessor property is defined |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
has => [ |
615
|
|
|
|
|
|
|
foo => { is => 'Some::Other', id_by => 'foo_id' }, |
616
|
|
|
|
|
|
|
], |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
Here, the 'foo' property requires another property called 'foo_id', which |
619
|
|
|
|
|
|
|
is not explicitly declared. In this case, the Property named foo_id will |
620
|
|
|
|
|
|
|
have its implied_by set to 'foo'. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=item id_by => ARRAY |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
In the case of an object accessor property, this is the list of properties in |
625
|
|
|
|
|
|
|
this class that link to the ID properties in the remote class. |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
=item reverse_as => Text |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
Defines the linking property name in the remote class in the case of an |
630
|
|
|
|
|
|
|
is_many relationship |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=item via => Text |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
For a via-type property, indicates which object accessor to go through. |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=item to => Text |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
For a via-type property, indicates the property name in the remote class to |
639
|
|
|
|
|
|
|
get its value from. The default value is the same as property_name |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
=item where => ARRAY |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
Supplies additional filters for indirect properties. For example: |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
foos => { is => 'Some::Foo', reverse_as => 'some', is_many => 1 }, |
646
|
|
|
|
|
|
|
blue_foos => { via => 'foos', where => [ color => 'blue' ] }, |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Would create a property 'blue_foos' which returns only the related |
649
|
|
|
|
|
|
|
Some::Foo objects that have 'blue' color. |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=item calculate_from => ARRAY |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
For calculated properties, this is a list of other property names the |
654
|
|
|
|
|
|
|
calculation is based on |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
=item calculate_perl => Text |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
For calculated properties, a string containing Perl code. Any properties |
659
|
|
|
|
|
|
|
mentioned in calculate_from will exist in the code's scope at run time |
660
|
|
|
|
|
|
|
as scalars of the same name. |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
=item class_meta => UR::Object::Type |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
Returns the class metaobject of the class this property belongs to |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
=back |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
=head1 METHODS |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
=over 4 |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
=item via_property_meta |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
For via/to delegated properties, return the property meta in the same |
675
|
|
|
|
|
|
|
class this property delegates through |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=item to_property_meta |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
For via/to delegated properties, return the property meta on the foreign |
680
|
|
|
|
|
|
|
class that this property delegates to |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
=back |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
=head1 SEE ALSO |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
UR::Object::Type, UR::Object::Type::Initializer, UR::Object |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=cut |