File Coverage

lib/UR/Role/Prototype.pm
Criterion Covered Total %
statement 538 560 96.0
branch 100 128 78.1
condition 27 50 54.0
subroutine 93 95 97.8
pod 5 6 83.3
total 763 839 90.9


line stmt bran cond sub pod time code
1             package UR::Role::Prototype;
2              
3 266     266   1010 use strict;
  266         364  
  266         6195  
4 266     266   820 use warnings;
  266         332  
  266         4926  
5              
6 266     266   841 use UR;
  266         325  
  266         1154  
7 266     266   971 use UR::Object::Type::InternalAPI;
  266         357  
  266         1481  
8 266     266   4375 use UR::Util;
  266         356  
  266         1059  
9 266     266   4441 use UR::AttributeHandlers;
  266         362  
  266         1152  
10              
11 266     266   4821 use Sub::Name qw();
  266         414  
  266         4691  
12 266     266   903 use Sub::Install qw();
  266         358  
  266         4211  
13 266     266   889 use List::MoreUtils qw(any);
  266         365  
  266         2275  
14 266     266   103569 use Carp;
  266         378  
  266         231610  
15             our @CARP_NOT = qw(UR::Object::Type);
16              
17             our $VERSION = "0.46"; # UR $VERSION;;
18              
19             UR::Object::Type->define(
20             class_name => 'UR::Role::Prototype',
21             doc => 'Object representing a role',
22             id_by => 'role_name',
23             has => [
24             id_by => { is => 'ARRAY', doc => 'List of ID properties and their definitions' },
25             role_name => { is => 'Text', doc => 'Package name identifying the role' },
26             class_names => { is => 'Text', is_many => 1, doc => 'Class names composing this role' },
27             methods => { is => 'HASH', doc => 'Map of method names and coderefs', default => {} },
28             overloads => { is => 'HASH', doc => 'Map of overload keys and coderefs', default => {} },
29             has => { is => 'ARRAY', doc => 'List of properties and their definitions' },
30             roles => { is => 'ARRAY', doc => 'List of other role names composed into this role', default => [] },
31             requires => { is => 'ARRAY', doc => 'List of properties required of consuming classes', default => [] },
32             attributes_have => { is => 'HASH', doc => 'Meta-attributes for properites' },
33             excludes => { is => 'ARRAY', doc => 'List of Role names that cannot compose with this role', default => [] },
34             method_modifiers => { is => 'UR::Role::MethodModifier',
35             is_many => 1,
36             doc => q(List of 'before', 'after' and 'around' method modifiers),
37             reverse_as => 'role'
38             },
39             map { $_ => _get_property_desc_from_ur_object_type($_) }
40             meta_properties_to_compose_into_classes(),
41             ],
42             is_transactional => 0,
43             );
44              
45             sub property_data {
46 23     23 1 28 my($self, $property_name) = @_;
47 23         44 return $self->has->{$property_name};
48             }
49              
50             sub has_property_names {
51 136     136 1 113 my $self = shift;
52 136         117 return keys %{ $self->has };
  136         226  
53             }
54              
55             sub id_by_property_names {
56 66     66 1 66 my $self = shift;
57 66         54 return @{ $self->id_by };
  66         145  
58             }
59              
60             sub method_names {
61 71     71 1 78 my $self = shift;
62 71         51 return keys %{ $self->methods };
  71         151  
63             }
64              
65             sub meta_properties_to_compose_into_classes {
66 591     591 0 2452 return qw( is_abstract is_final is_singleton doc
67             composite_id_separator id_generator valid_signals
68             subclassify_by subclass_description_preprocessor sub_classification_method_name
69             sub_classification_meta_class_name
70             schema_name data_source_id table_name select_hint join_hint );
71             }
72              
73             sub define {
74 42     42 1 63 my $class = shift;
75 42         117 my $desc = $class->_normalize_role_description(@_);
76              
77 42 50       111 unless ($desc->{role_name}) {
78 0         0 Carp::croak(q('role_name' is a required parameter for defining a role));
79             }
80              
81 42         103 my $methods = _introspect_methods($desc->{role_name});
82 42         109 my $overloads = _introspect_overloads($desc->{role_name});
83              
84 42         1238 my $extra = delete $desc->{extra};
85 42         286 my $role = UR::Role::Prototype->__define__(%$desc, methods => $methods, overloads => $overloads);
86              
87 42 100 66     225 if ($extra and %$extra) {
88 1         5 $role->UR::Object::Type::_apply_extra_attrs_to_class_or_role($extra);
89             }
90              
91 41         103 $role->_inject_instance_constructor_into_namespace();
92              
93 41         1444 return $role;
94             }
95              
96             our @ROLE_DESCRIPTION_KEY_MAPPINGS = (
97             @UR::Object::Type::CLASS_DESCRIPTION_KEY_MAPPINGS_COMMON_TO_CLASSES_AND_ROLES,
98             [ role_name => qw// ],
99             [ methods => qw// ],
100             [ requires => qw// ],
101             [ excludes => qw// ],
102             );
103              
104             sub _normalize_role_description {
105 42     42   53 my $class = shift;
106 42         99 my $old_role = { @_ };
107              
108 42         102 my $role_name = delete $old_role->{role_name};
109              
110 42         142 my $new_role = {
111             role_name => $role_name,
112             has => {},
113             attributes_have => {},
114             UR::Object::Type::_canonicalize_class_params($old_role, \@ROLE_DESCRIPTION_KEY_MAPPINGS),
115             };
116              
117             # The above call to _canonicalize_class_params removed recognized keys. Anything
118             # left over wasn't recognized
119 42         70 $new_role->{extra} = $old_role;
120              
121 42         66 foreach my $key (qw( requires excludes ) ) {
122 84 50       170 unless (UR::Util::ensure_arrayref($new_role, $key)) {
123 0         0 Carp::croak("The '$key' metadata for role $role_name must be an arrayref");
124             }
125             }
126              
127             # UR::Object::Type::_normalize_class_description_impl() copies these over before
128             # processing the properties. We need to, too
129 42         107 @$old_role{'has', 'attributes_have'} = @$new_role{'has','attributes_have'};
130 42         101 @$new_role{'has','attributes_have'} = ( {}, {} );
131 42         101 UR::Object::Type::_massage_field_into_arrayref($new_role, 'id_by');
132 42         93 UR::Object::Type::_normalize_id_property_data($old_role, $new_role);
133 42         101 UR::Object::Type::_process_class_definition_property_keys($old_role, $new_role);
134 42         98 _complete_property_descriptions($new_role);
135              
136 42         68 return $new_role;
137             }
138              
139             sub _complete_property_descriptions {
140 42     42   47 my $role_desc = shift;
141              
142             # stole from UR::Object::Type::_normalize_class_description_impl()
143 42         64 my $properties = $role_desc->{has};
144 42         74 foreach my $property_name ( keys %$properties ) {
145 17         25 my $old_property = $properties->{$property_name};
146 17         72 my %new_property = UR::Object::Type->_normalize_property_description1($property_name, $old_property, $role_desc);
147 17         45 delete $new_property{class_name}; # above normalizer fills this in as undef
148 17         43 $properties->{$property_name} = \%new_property;
149             }
150             }
151              
152             my %property_definition_key_to_method_name = (
153             is => 'data_type',
154             len => 'data_length',
155             );
156              
157             sub _get_property_desc_from_ur_object_type {
158 4256     4256   3908 my $property_name = shift;
159              
160 4256         10147 my $prop_meta = UR::Object::Property->get(class_name => 'UR::Object::Type', property_name => $property_name);
161 4256 50       7430 Carp::croak("Couldn't get UR::Object::Type property meta for $property_name") unless $prop_meta;
162              
163             # These properties' definition key is the same as the method name
164 22344         30771 my %definition = map { $_ => $prop_meta->$_ }
165 4256         4991 grep { defined $prop_meta->$_ }
  25536         44435  
166             qw( is_many is_optional is_transient is_mutable default_value doc );
167              
168             # These have a translation
169 4256         11179 while(my($key, $method) = each(%property_definition_key_to_method_name)) {
170 0         0 $definition{$key} = $prop_meta->$method;
171             }
172              
173             # For any UR::Object::Type properties that are required or have a default value,
174             # those don't apply to Roles
175 4256         4214 $definition{is_optional} = 1;
176 4256         3811 delete $definition{default_value};
177              
178 4256         10271 return \%definition;
179             }
180              
181             {
182             my @overload_ops;
183             sub _all_overload_ops {
184 53 100   53   132 @overload_ops = map { split /\s+/ } values(%overload::ops) unless @overload_ops;
  45         161  
185 53         847 @overload_ops;
186             }
187             }
188              
189             my @DONT_EXPORT_THESE_SUBS_TO_CLASSES = qw(__import__ FETCH_CODE_ATTRIBUTES MODIFY_CODE_ATTRIBUTES MODIFY_SCALAR_ATTRIBUTES before after around);
190             sub _introspect_methods {
191 42     42   54 my $role_name = shift;
192              
193 42         108 my $subs = UR::Util::coderefs_for_package($role_name);
194 42         107 delete @$subs{@DONT_EXPORT_THESE_SUBS_TO_CLASSES}; # don't allow __import__ to be exported to a class's namespace
195 42         99 delete @$subs{ map { "($_" } ( _all_overload_ops, ')', '(' ) };
  3234         2760  
196 42         280 return $subs;
197             }
198              
199             sub _introspect_overloads {
200 84     84   117 my $role_name = shift;
201              
202 84 100       253 return {} unless overload::Overloaded($role_name);
203              
204 11         304 my %overloads;
205 11         13 my $stash = do {
206 266     266   1362 no strict 'refs';
  266         417  
  266         309582  
207 11         11 \%{$role_name . '::'};
  11         22  
208             };
209 11         17 foreach my $op ( _all_overload_ops ) {
210 825 100       736 my $op_key = $op eq 'fallback' ? ')' : $op;
211 825         585 my $overloaded = $stash->{'(' . $op_key};
212              
213 825 100       920 if ($overloaded) {
214 16         11 my $subref = *{$overloaded}{CODE};
  16         29  
215             $overloads{$op} = $subref eq \&overload::nil
216 16 100       46 ? ${*{$overloaded}{SCALAR}} # overridden with string method name
  14         10  
  14         47  
217             : $subref; # overridden with a subref
218             }
219             }
220 11         47 return \%overloads;
221             }
222              
223             # Called by UR::Object::Type::Initializer::compose_roles to apply a role name
224             # to a partially constructed class description
225             sub _apply_roles_to_class_desc {
226 12763     12763   16016 my($class, $desc) = @_;
227 12763 50 33     60632 if (ref($class) or ref($desc) ne 'HASH') {
228 0         0 Carp::croak('_apply_roles_to_class_desc() must be called as a class method on a basic class description');
229             }
230              
231 12763         32062 _validate_class_method_overrides_consumed_roles($desc);
232 12762 100 66     31559 return unless ($desc->{roles} and @{ $desc->{roles} });
  12762         49622  
233 59         136 my @role_objs = _role_prototypes_with_params_for_class_desc($desc);
234              
235 56         132 _validate_role_exclusions($desc, @role_objs);
236 54         106 _validate_role_requirements($desc, @role_objs);
237 51         115 _validate_class_desc_overrides($desc, @role_objs);
238              
239 47         142 my $id_property_names_to_add = _collect_id_property_names_from_roles($desc, @role_objs);
240 46         108 my $properties_to_add = _collect_properties_from_roles($desc, @role_objs);
241 43         2409 my $meta_properties_to_add = _collect_meta_properties_from_roles($desc, @role_objs);
242 42         991 my $overloads_to_add = _collect_overloads_from_roles($desc, @role_objs);
243 40         82 my $method_modifiers_to_add = _collect_method_modifiers_from_roles($desc, @role_objs);
244              
245 39         124 _save_role_instances_to_class_desc($desc, @role_objs);
246 39         115 _assert_all_role_params_are_bound_to_values($desc, @role_objs);
247 36         60 do { $_->prototype->add_class_name($desc->{class_name}) } foreach @role_objs;
  49         117  
248              
249 36         167 UR::Role::Param->replace_unbound_params_in_struct_with_values(
250             [ $id_property_names_to_add, $properties_to_add, $meta_properties_to_add, $overloads_to_add ],
251             @role_objs);
252              
253 36         130 _import_methods_from_roles_into_namespace($desc->{class_name}, \@role_objs);
254 35         787 _apply_overloads_to_namespace($desc->{class_name}, $overloads_to_add);
255 35         86 _apply_method_modifiers_to_namespace($desc, $method_modifiers_to_add);
256              
257 35         111 _merge_role_meta_properties_into_class_desc($desc, $meta_properties_to_add);
258 35         89 _merge_role_id_property_names_into_class_desc($desc, $id_property_names_to_add);
259 35         79 _merge_role_properties_into_class_desc($desc, $properties_to_add);
260             }
261              
262             sub _save_role_instances_to_class_desc {
263 39     39   57 my($desc, @role_prototypes) = @_;
264              
265 39         57 my $class_name = $desc->{class_name};
266 39         70 my @instances = map { $_->instantiate_role_instance($class_name) }
  52         197  
267             @role_prototypes;
268 39         103 $desc->{roles} = \@instances;
269             }
270              
271             sub _assert_all_role_params_are_bound_to_values {
272 39     39   85 my($desc, @role_instances) = @_;
273              
274 39         96 foreach my $instance ( @role_instances ) {
275 52         133 my $role_name = $instance->role_name;
276 52         239 my %expected_params = map { $_ => 1 }
  5         14  
277             UR::Role::Param->param_names_for_role($role_name);
278 52         108 my $got_params = $instance->role_params;
279 52 100       140 if (my @missing = grep { ! exists($got_params->{$_}) } keys %expected_params) {
  5         18  
280 2         28 Carp::croak("Role $role_name expects values for these params: ",join(', ', @missing));
281             }
282 50 100       158 if (my @extra = grep { ! exists($expected_params{$_}) } keys %$got_params) {
  4         15  
283 1         17 Carp::croak("Role $role_name does not recognize these params: ",join(', ', @extra));
284             }
285             }
286             }
287              
288              
289             sub _merge_role_meta_properties_into_class_desc {
290 35     35   38 my($desc, $meta_properties_to_add) = @_;
291              
292 35         71 my $valid_signals = delete $meta_properties_to_add->{valid_signals};
293 35         81 my @meta_prop_names = keys %$meta_properties_to_add;
294 35         67 @$desc{@meta_prop_names} = @$meta_properties_to_add{@meta_prop_names};
295 35 100       85 if ($valid_signals) {
296 3         4 push @{$desc->{valid_signals}}, @$valid_signals;
  3         7  
297             };
298             }
299              
300             sub _merge_role_properties_into_class_desc {
301 35     35   38 my($desc, $properties_to_add) = @_;
302              
303 35         70 my @property_names = keys %$properties_to_add;
304 35         43 @{$desc->{has}}{@property_names} = @$properties_to_add{@property_names};
  35         172  
305             }
306              
307             sub _merge_role_id_property_names_into_class_desc {
308 35     35   42 my($desc, $id_properties_to_add) = @_;
309              
310 35         57 push @{$desc->{id_by}}, @$id_properties_to_add;
  35         75  
311             }
312              
313             sub _role_prototypes_with_params_for_class_desc {
314 59     59   75 my $desc = shift;
315              
316 59         80 my @role_prototypes;
317 59         52 foreach my $role_name ( @{ $desc->{roles} } ) {
  59         93  
318 81         76 my $role;
319 81         65 my $exception = do {
320 81         70 local $@;
321 81         91 $role = eval { $role_name->__role__ };
  81         220  
322 81         125 $@;
323             };
324 81 100       138 unless ($role) {
325 3         6 my $class_name = $desc->{class_name};
326 3         31 Carp::croak("Cannot apply role $role_name to class $class_name: $exception");
327             }
328 78         122 push @role_prototypes, $role;
329             }
330 56         97 return @role_prototypes;
331             }
332              
333             sub _collect_id_property_names_from_roles {
334 47     47   76 my($desc, @role_objs) = @_;
335              
336 47         66 my %class_id_by_properties = map { $_ => 1 } @{ $desc->{id_by} };
  2         7  
  47         96  
337 13         34 my %class_property_is_id_by = map { $_ => $class_id_by_properties{$_} }
338 47         66 keys %{ $desc->{has} };
  47         102  
339              
340 47         58 my @property_names_to_add;
341 47         84 foreach my $role ( @role_objs ) {
342 66         157 my @role_id_property_names = $role->id_by_property_names;
343              
344 66 100       90 my @conflict = grep { exists($class_property_is_id_by{$_}) and ! $class_property_is_id_by{$_} }
  2         120  
345             @role_id_property_names;
346 66 100       124 if (@conflict) {
347             Carp::croak(sprintf(q(Cannot compose role %s: Property '%s' was declared as a normal property in class %s, but as an ID property in the role),
348             $role->role_name,
349             join(q(', '), @conflict),
350             $desc->{class_name},
351 1         3 ));
352             }
353 65         98 push @property_names_to_add, @role_id_property_names;
354             }
355 46         94 return \@property_names_to_add;
356             }
357              
358             sub _collect_properties_from_roles {
359 46     46   62 my($desc, @role_objs) = @_;
360              
361 46         73 my $properties_from_class = $desc->{has};
362              
363 46         51 my(%properties_to_add, %source_for_properties_to_add);
364 46         78 foreach my $role ( @role_objs ) {
365 65         136 my @role_property_names = $role->has_property_names;
366 65         88 foreach my $property_name ( @role_property_names ) {
367 23         54 my $prop_definition = $role->property_data($property_name);
368 23 100       46 if (my $conflict = $source_for_properties_to_add{$property_name}) {
369 3         8 Carp::croak(sprintf(q(Cannot compose role %s: Property '%s' conflicts with property in role %s),
370             $role->role_name, $property_name, $conflict));
371             }
372              
373 20         41 $source_for_properties_to_add{$property_name} = $role->role_name;
374              
375 20 100       46 next if exists $properties_from_class->{$property_name};
376              
377 17         42 $properties_to_add{$property_name} = $prop_definition;
378             }
379             }
380 43         130 return UR::Util::deep_copy(\%properties_to_add);
381             }
382              
383             sub _collect_overloads_from_roles {
384 42     42   65 my($desc, @role_objs) = @_;
385              
386 42         99 my $overloads_from_class = _introspect_overloads($desc->{class_name});
387              
388 42         1648 my(%overloads_to_add, %source_for_overloads_to_add);
389 42         94 my $fallback_validator = _create_fallback_validator();
390              
391 42         65 foreach my $role ( @role_objs ) {
392 57         126 my $role_name = $role->role_name;
393 57         121 my $overloads_this_role = $role->overloads;
394              
395 57         153 $fallback_validator->($role_name, $overloads_this_role->{fallback});
396 56         207 while( my($op, $impl) = each(%$overloads_this_role)) {
397 26 100       54 next if ($op eq 'fallback');
398 16 100       33 if (my $conflict = $source_for_overloads_to_add{$op}) {
399 1         12 Carp::croak("Cannot compose role $role_name: Overload '$op' conflicts with overload in role $conflict");
400             }
401 15         18 $source_for_overloads_to_add{$op} = $role_name;
402              
403 15 100       23 next if exists $overloads_from_class->{$op};
404              
405 14         40 $overloads_to_add{$op} = $impl;
406             }
407             }
408              
409 40         57 my $fallback = $fallback_validator->();
410 40 100       76 $overloads_to_add{fallback} = $fallback if defined $fallback;
411 40         279 return \%overloads_to_add;
412             }
413              
414             sub _collect_method_modifiers_from_roles {
415 40     40   69 my($desc, @role_objs) = @_;
416              
417 40         78 my $class_name = $desc->{class_name};
418 40         68 my @all_modifiers = map { $_->method_modifiers } @role_objs;
  53         148  
419              
420 40         101 my $isa = join('::', $class_name, 'ISA');
421 266     266   1355 no strict 'refs';
  266         444  
  266         10738  
422 40         106 local @$isa = (@$isa, @{$desc->{is}});
  40         742  
423 266     266   1005 use strict 'refs';
  266         363  
  266         409550  
424              
425 40         141 foreach my $mod ( @all_modifiers ) {
426 6 100       30 unless ($class_name->can($mod->name)) {
427 1         56 my $role_name = $mod->role->role_name;
428 1         6 my $type = $mod->type;
429 1         4 my $subname = $mod->name;
430 1         21 Carp::croak(qq(Cannot compose role $role_name: Cannot apply '$type' method modifier: Method "$subname" not found via class $class_name));
431             }
432             }
433 39         229 return \@all_modifiers;
434             }
435              
436             sub _create_fallback_validator {
437 42     42   59 my($fallback, $fallback_set_in);
438              
439             return sub {
440 97 100   97   201 unless (@_) {
441             # no args, return current value
442 40         85 return $fallback;
443             }
444              
445 57         121 my($role_name, $value) = @_;
446 57 100 100     159 if (defined($value) and !defined($fallback)) {
447 5         6 $fallback = $value;
448 5         6 $fallback_set_in = $role_name;
449 5         6 return 1;
450             }
451 52 100 100     148 return 1 unless (defined($fallback) and defined ($value));
452 3 100 75     15 return 1 unless ($fallback xor $value);
453              
454 1 0       21 Carp::croak(sprintf(q(Cannot compose role %s: fallback value '%s' conflicts with fallback value '%s' in role %s),
    50          
    50          
    50          
455             $role_name,
456             $value ? $value : defined($value) ? 'FALSE' : 'UNDEF',
457             $fallback ? $fallback : defined($fallback) ? 'FALSE' : 'UNDEF',
458             $fallback_set_in));
459 42         177 };
460             }
461              
462              
463             sub _collect_meta_properties_from_roles {
464 43     43   75 my($desc, @role_objs) = @_;
465              
466 43         50 my(%meta_properties_to_add, %source_for_meta_properties_to_add);
467 43         75 foreach my $role ( @role_objs ) {
468 59         164 foreach my $meta_prop_name ( $role->meta_properties_to_compose_into_classes ) {
469 934 100 100     1735 next if (defined $desc->{$meta_prop_name} and $meta_prop_name ne 'valid_signals');
470 751 100       1325 next unless defined $role->$meta_prop_name;
471              
472 16 100       22 if ($meta_prop_name ne 'valid_signals') {
473 12 100       22 if (exists $meta_properties_to_add{$meta_prop_name}) {
474             Carp::croak(sprintf(q(Cannot compose role %s: Meta property '%s' conflicts with meta property from role %s),
475             $role->role_name,
476             $meta_prop_name,
477 1         4 $source_for_meta_properties_to_add{$meta_prop_name}));
478             }
479 11         18 $meta_properties_to_add{$meta_prop_name} = $role->$meta_prop_name;
480 11         18 $source_for_meta_properties_to_add{$meta_prop_name} = $role->role_name;
481             } else {
482 4   50     15 $meta_properties_to_add{valid_signals} ||= [];
483 4         5 push @{ $meta_properties_to_add{valid_signals} }, @{ $role->valid_signals };
  4         5  
  4         6  
484             }
485             }
486             }
487 42         104 return UR::Util::deep_copy(\%meta_properties_to_add);
488             }
489              
490             sub _validate_role_requirements {
491 54     54   80 my($desc, @role_objs) = @_;
492              
493 54         92 my $class_name = $desc->{class_name};
494 54         60 my %found_properties_and_methods = map { $_ => 1 } keys %{ $desc->{has} };
  16         33  
  54         101  
495              
496 54         96 foreach my $role ( @role_objs ) {
497 74         88 foreach my $requirement ( @{ $role->requires } ) {
  74         157  
498 15 100 100     52 unless ($found_properties_and_methods{ $requirement }
499             ||= _class_desc_lineage_has_method_or_property($desc, $requirement))
500             {
501 3         8 my $role_name = $role->role_name;
502 3         36 Carp::croak("Cannot compose role $role_name: missing required property or method '$requirement'");
503             }
504             }
505              
506             # Properties and methods from this role can satisfy requirements for later roles
507 71         154 foreach my $name ( $role->has_property_names, $role->method_names ) {
508 72         113 $found_properties_and_methods{$name} = 1;
509             }
510             }
511              
512 51         75 return 1;
513             }
514              
515             sub _validate_role_exclusions {
516 56     56   81 my($desc, @role_objs) = @_;
517              
518 56         58 my %role_names = map { $_ => $_ } @{ $desc->{roles} };
  78         174  
  56         122  
519 56         90 foreach my $role ( @role_objs ) {
520              
521 3         6 my @conflicts = grep { defined }
522 77         76 @role_names{ @{ $role->excludes } };
  77         192  
523 77 100       161 if (@conflicts) {
524 2         3 my $class_name = $desc->{class_name};
525 2 50       7 my $plural = @conflicts > 1 ? 's' : '';
526             Carp::croak(sprintf('Cannot compose role%s %s into class %s: Role %s excludes %s',
527             $plural,
528             join(', ', @conflicts),
529             $desc->{class_name},
530 2 50       6 $role->role_name,
531             $plural ? 'them' : 'it'));
532             }
533             }
534 54         78 return 1;
535             }
536              
537             sub _validate_class_method_overrides_consumed_roles {
538 12763     12763   14872 my $desc = shift;
539              
540 12763         20294 my $class_name = $desc->{class_name};
541             my %this_class_role_names = $desc->{roles}
542 81 100       285 ? map { ref($_) ? ($_->role_name => 1) : ($_ => 1) }
543 12763 50       27189 @{$desc->{roles}}
  12763         27406  
544             : ();
545 12763         36640 my $this_class_methods = UR::Util::coderefs_for_package($class_name);
546 12763         45328 while (my($method_name, $subref) = each %$this_class_methods) {
547 79076         100125 my @overrides = UR::AttributeHandlers::get_overrides_for_coderef($subref);
548 79076 100       172322 next unless (@overrides);
549              
550 7         9 my @missing_role_names = grep { ! exists $this_class_role_names{$_} }
  8         20  
551             @overrides;
552 7 100       25 if (@missing_role_names) {
553 1         14 Carp::croak("Class method '$method_name' declares Overrides for roles the class does not consume: "
554             . join(', ', @missing_role_names));
555             }
556             }
557 12762         27758 return 1;
558             }
559              
560             sub _validate_class_desc_overrides {
561 51     51   77 my($desc, @roles) = @_;
562              
563 51         66 my $class_name = $desc->{class_name};
564 102         77 my %this_class_methods = map { %{ UR::Util::coderefs_for_package($_) } }
  102         239  
565 51         50 (@{$desc->{is}}, $class_name);
  51         86  
566              
567 51         175 my %overridden_methods_by_role;
568 51         246 foreach my $method_name ( keys %this_class_methods ) {
569 2573 100       2884 if (my @role_names = UR::AttributeHandlers::get_overrides_for_coderef($this_class_methods{$method_name})) {
570 6         12 foreach my $role_name ( @role_names ) {
571 7   50     34 $overridden_methods_by_role{$role_name} ||= [];
572 7         7 push @{$overridden_methods_by_role{$role_name}}, $method_name;
  7         23  
573             }
574             }
575             }
576              
577 51         133 foreach my $role ( @roles ) {
578 71         213 my $role_name = $role->role_name;
579 71         162 my $this_role_methods = $role->methods;
580 71         138 my @this_role_method_names = keys( %$this_role_methods );
581              
582 71         75 my %method_is_overridden;
583 8   66     35 my @conflict_methods = grep { ! ($method_is_overridden{$_} ||= _coderef_overrides_package($this_class_methods{$_}, $role_name)) }
584 71         99 grep { exists $this_class_methods{$_} }
  45         107  
585             @this_role_method_names;
586 71 100       138 if (@conflict_methods) {
587 3 50       11 my $plural = scalar(@conflict_methods) > 1 ? 's' : '';
588 3 50       8 my $conflicts = scalar(@conflict_methods) > 1 ? 'conflict' : 'conflicts';
589              
590 3         3 my %conflicting_sources;
591             CONFLICTING_METHOD_NAME:
592 3         4 foreach my $conflicting_method_name ( @conflict_methods ) {
593 3         4 foreach my $source_class_name ( $class_name, @{$desc->{is}} ) {
  3         8  
594 4 100       80 if ($source_class_name->can($conflicting_method_name)) {
595 3         37 $conflicting_sources{$conflicting_method_name} = $source_class_name;
596 3         7 next CONFLICTING_METHOD_NAME;
597             }
598             }
599 0         0 $conflicting_sources{$conflicting_method_name} = '';
600             }
601             Carp::croak("Cannot compose role $role_name: "
602             . "Method name${plural} $conflicts with class $class_name:\n"
603 3         17 . join("\n", map { sprintf("\t%s (from %s)\n", $_, $conflicting_sources{$_}) }
  3         71  
604             keys %conflicting_sources)
605             . "Did you forget to add the 'Overrides' attribute?");
606             }
607              
608 7   100     26 my @missing_methods = grep { ! exists $this_role_methods->{$_} and ! exists $role->has->{$_} }
609 68         61 @{$overridden_methods_by_role{$role_name}};
  68         144  
610 68 100       175 if (@missing_methods) {
611 1 50       4 my $plural = scalar(@missing_methods) > 1 ? 's' : '';
612 1         3 my $method_list = join(q(', '), @missing_methods);
613 1         26 Carp::croak("Cannot compose role $role_name: "
614             . "Class method${plural} '$method_list' declares it Overrides non-existant method in the role.");
615             }
616             }
617              
618 47         211 return 1;
619             }
620              
621             sub _class_desc_lineage_has_method_or_property {
622 10     10   12 my($desc, $requirement) = @_;
623              
624 10         15 my $class_name = $desc->{class_name};
625 10 100       50 if (my $can = $class_name->can($requirement)) {
626 5         73 return $can;
627             }
628              
629 5         167 my @is = @{ $desc->{is} };
  5         9  
630 5         7 my %seen;
631 5         10 while(my $parent = shift @is) {
632 5 50       13 next if $seen{$parent}++;
633              
634 5 100       12 if (my $can = $parent->can($requirement)) {
635 2         19 return $can;
636             }
637              
638 3         91 my $parent_meta = $parent->__meta__;
639 3 50       10 if (my $prop_meta = $parent_meta->property($requirement)) {
640 0         0 return $prop_meta;
641             }
642             }
643 3         12 return;
644             }
645              
646             sub _import_methods_from_roles_into_namespace {
647 36     36   46 my($class_name, $roles) = @_;
648              
649 36         86 my $this_class_methods = UR::Util::coderefs_for_package($class_name);
650              
651 36         45 my(%all_imported_methods, %method_sources);
652 36         61 foreach my $role ( @$roles ) {
653 49         110 my $this_role_methods = $role->methods;
654 49         99 my @this_role_method_names = keys( %$this_role_methods );
655              
656 2         4 my @conflicting = grep { ! exists($this_class_methods->{$_}) } # not a conflict if the class overrides
657 49         73 grep { exists $all_imported_methods{$_} }
  30         60  
658             @this_role_method_names;
659              
660 49 100       97 if (@conflicting) {
661 1 50       3 my $plural = scalar(@conflicting) > 1 ? 's' : '';
662 1 50       3 my $conflicts = scalar(@conflicting) > 1 ? 'conflict' : 'conflicts';
663             Carp::croak('Cannot compose role ' . $role->role_name
664             . ": method${plural} $conflicts with those defined in other roles\n\t"
665 1         3 . join("\n\t", join('::', map { ( $method_sources{$_}, $_ ) } @conflicting)));
  1         12  
666             }
667              
668 48         118 @method_sources{ @this_role_method_names } = ($role->role_name) x @this_role_method_names;
669 48         95 @all_imported_methods{ @this_role_method_names } = @$this_role_methods{ @this_role_method_names };
670             }
671              
672 35         62 delete @all_imported_methods{ keys %$this_class_methods }; # Don't import roles' methods already defined on the class
673 35         87 foreach my $name ( keys %all_imported_methods ) {
674             Sub::Install::install_sub({
675 24         604 code => $all_imported_methods{$name},
676             as => $name,
677             into => $class_name,
678             });
679             }
680             }
681              
682             sub _coderef_overrides_package {
683 8     8   14 my($coderef, $package) = @_;
684              
685 8         17 my @overrides = UR::AttributeHandlers::get_overrides_for_coderef($coderef);
686 8     7   54 return any { $_ eq $package } @overrides;
  7         32  
687             }
688              
689             sub _apply_overloads_to_namespace {
690 35     35   48 my($class_name, $overloads) = @_;
691              
692 35         37 my(%cooked_overloads);
693 35         116 while( my($op, $impl) = each %$overloads) {
694 16 100       53 $cooked_overloads{$op} = ref $impl
695             ? sprintf(q($overloads->{'%s'}), $op)
696             : qq('$impl');
697             }
698              
699             my $string = "package $class_name;\n"
700             . 'use overload '
701 35         131 . join(",\n\t", map { sprintf(q('%s' => %s), $_, $cooked_overloads{$_}) } keys %cooked_overloads)
  16         50  
702             . ';';
703              
704 35         35 my $exception;
705 35         33 do {
706 35         40 local $@;
707 35     79   2833 eval $string;
  1     79   5  
  1     79   2  
  1     79   3  
  1     79   5  
  1     79   2  
  1     79   4  
  1     79   6  
  1     79   2  
  1     79   5  
  1     79   6  
  1     79   1  
  1     79   5  
  1     79   6  
  1     79   2  
  1     79   5  
  1     79   6  
  1     79   1  
  1     79   4  
  1     79   7  
  1     79   1  
  1     79   7  
  1     79   7  
  1     79   1  
  1     79   5  
  1     79   6  
  1     79   1  
  1     79   5  
  1     79   5  
  1     79   1  
  1     79   5  
  1     79   7  
  1     79   1  
  1     79   7  
  1     78   6  
  1         1  
  1         6  
  1         6  
  1         1  
  1         4  
  1         6  
  1         1  
  1         6  
  1         7  
  1         1  
  1         6  
  1         8  
  1         1  
  1         7  
  1         7  
  1         2  
  1         7  
  1         5  
  1         3  
  1         4  
  1         6  
  1         2  
  1         4  
  1         5  
  1         2  
  1         4  
  1         5  
  1         1  
  1         4  
  1         5  
  1         1  
  1         5  
  1         7  
  1         2  
  1         4  
  1         8  
  1         2  
  1         5  
  1         7  
  1         1  
  1         4  
  1         5  
  1         1  
  1         4  
  1         6  
  1         2  
  1         3  
  1         6  
  1         2  
  1         4  
  1         5  
  1         1  
  1         5  
  1         7  
  1         1  
  1         6  
  1         8  
  1         2  
  1         6  
  1         8  
  1         1  
  1         6  
  1         4  
  1         2  
  1         5  
  1         8  
  1         1  
  1         7  
  1         8  
  1         1  
  1         6  
708 35         1759 $exception = $@;
709             };
710              
711 35 50       82 if ($exception) {
712 0         0 Carp::croak("Failed to apply overloads to package $class_name: $exception");
713             }
714 35         66 return 1;
715             }
716              
717             sub _apply_method_modifiers_to_namespace {
718 35     35   46 my($desc, $modifiers_list) = @_;
719              
720 35         65 my $class_name = $desc->{class_name};
721              
722 35         83 my $isa = join('::', $class_name, 'ISA');
723 266     266   1428 no strict 'refs';
  266         417  
  266         9898  
724 35         81 local @$isa = (@$isa, @{$desc->{is}});
  35         535  
725 266     266   1088 use strict 'refs';
  266         451  
  266         82637  
726              
727 35         133 foreach my $mod ( @$modifiers_list ) {
728 5         35 $mod->apply_to_package($class_name);
729             }
730 35         540 1;
731             }
732              
733             sub _define_role {
734 0     0   0 my($role_name, $func, @params) = @_;
735              
736 0 0 0     0 if (defined($func) and $func eq "role" and @params > 1 and $role_name ne "UR::Role") {
      0        
      0        
737 0         0 my @role_params;
738 0 0 0     0 if (@params == 2 and ref($params[1]) eq 'HASH') {
    0 0        
739 0         0 @role_params = %{ $params[1] };
  0         0  
740             }
741             elsif (@params == 2 and ref($params[1]) eq 'ARRAY') {
742 0         0 @role_params = @{ $params[1] };
  0         0  
743             }
744             else {
745 0         0 @role_params = @params[1..$#params];
746             }
747 0         0 my $role = UR::Role->define(role_name => $role_name, @role_params);
748 0 0       0 unless ($role) {
749 0         0 Carp::croak "error defining role $role_name!";
750             }
751 0     0   0 return sub { $role_name };
  0         0  
752             } else {
753 0         0 return;
754             }
755             }
756              
757             sub _inject_instance_constructor_into_namespace {
758 41     41   56 my $self = shift;
759              
760 41         126 my $package = $self->role_name;
761 41         94 my $full_name = join('::', $package, 'create');
762             my $sub = Sub::Name::subname $full_name => sub {
763 78     78   2837 my($class, %params) = @_;
        79      
        78      
        78      
        78      
764 78         296 return UR::Role::PrototypeWithParams->create(prototype => $self, role_params => \%params);
765 41         347 };
766 41         214 Sub::Install::reinstall_sub({
767             into => $package,
768             as => 'create',
769             code => $sub,
770             });
771              
772             Sub::Install::reinstall_sub({
773             into => $package,
774             as => '__role__',
775 74     74   197 code => sub { $package->create() },
776 41         2191 });
777             }
778              
779             1;
780              
781             __END__