File Coverage

blib/lib/Util/DataThing/Type.pm
Criterion Covered Total %
statement 103 122 84.4
branch 21 40 52.5
condition 2 14 14.2
subroutine 23 28 82.1
pod 0 13 0.0
total 149 217 68.6


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Util::DataThing::Type - Represents the type of a property in L
5              
6             =cut
7              
8             package Util::DataThing::Type;
9              
10 4     4   54998 use strict;
  4         10  
  4         202  
11 4     4   27 use warnings;
  4         9  
  4         177  
12 4     4   27 use Carp qw(croak confess);
  4         9  
  4         995  
13 4     4   8775 use overload "<=>" => \&_compare, '""' => sub { $_[0]->{display_name} };
  4     2   5851  
  4         52  
  2         247  
14 4     4   310 use Scalar::Util;
  4         6  
  4         291  
15 4     4   4242 use Sub::Name;
  4         3071  
  4         357  
16 4     4   3363 use MRO::Compat;
  4         12923  
  4         1107  
17              
18             my %primitives;
19             my %primitive_coerce;
20             my %objects;
21             my %object_properties;
22              
23             BEGIN {
24              
25 4     4   19 %primitives = ();
26             %primitive_coerce = (
27             string => sub {
28 17         98 return "".$_[0];
29             },
30             integer => sub {
31 4         15 return int($_[0])+0;
32             },
33             float => sub {
34 0         0 return $_[0]+0;
35             },
36             boolean => sub {
37 6 100       31 return $_[0] ? 1 : 0;
38             },
39             any => sub {
40 0         0 return $_[0];
41             },
42 4         65 );
43 4         11 %objects = ();
44 4         32 %object_properties = ();
45              
46              
47 4         12 foreach my $type_name (qw(string integer float boolean any)) {
48 20         58 my $obj = bless {}, __PACKAGE__;
49 20         221 $obj->{display_name} = '('.$type_name.')';
50 20         32 my $coerce = $primitive_coerce{$type_name};
51 20         117 Sub::Name::subname("_coerce_".$type_name, $coerce);
52 20         36 $obj->{coerce_in} = $coerce;
53 20         26 $obj->{coerce_out} = $coerce;
54              
55 20         37 $primitives{$type_name} = $obj;
56              
57             # We also create a convenient constant in Util::DataThing
58             # so that subclasses can write, for example, STRING instead
59             # of Util::DataThing::Type->primitive('string').
60 20         37 my $sub_name = 'Util::DataThing::'.uc($type_name);
61             {
62 4     4   34 no strict 'refs';
  4         7  
  4         245  
  20         22  
63 20     9   51 *{$sub_name} = sub { $obj };
  20         5576  
  9         1734  
64             }
65             }
66             }
67              
68             sub primitive {
69 0     0 0 0 my ($class, $type_name) = @_;
70              
71 0 0       0 return $primitives{$type_name} or croak "There is no primitive type called $type_name";
72             }
73              
74             sub object {
75 15     15 0 22 my ($class, $object_class) = @_;
76              
77 15 100       80 return $objects{$object_class} if defined($objects{$object_class});
78              
79 5         17 my $self = bless {}, __PACKAGE__;
80 5         124 $self->{display_name} = $object_class;
81 5         11 $self->{object_class} = $object_class;
82             $self->{coerce_in} = sub {
83 2     2   3 my ($value) = @_;
84              
85 2         6 my $class = $self->object_class;
86 2 50       20 confess("Only $class objects can be assigned to this field.") unless UNIVERSAL::isa($value, $class);
87              
88 2         9 return $value->{data};
89 5         25 };
90             $self->{coerce_out} = sub {
91 8     8   8 my ($value) = @_;
92              
93 8         17 my $class = $self->object_class;
94 8         15 my $obj = { data => $value };
95 8         46 return bless $obj, $class;
96 5         22 };
97              
98 5         28 Sub::Name::subname("_coerce_in_object", $self->{coerce_in});
99 5         21 Sub::Name::subname("_coerce_out_object", $self->{coerce_out});
100              
101             # Ensure that we only end up with one instance in memory for each
102             # class at any time, but also that we don't end up with
103             # a giant cache of unused types.
104 5         9 $objects{$object_class} = $self;
105 5         18 Scalar::Util::weaken($objects{$object_class});
106              
107 5         24 return $self;
108             }
109              
110             sub is_object {
111 14 50   14 0 103 return defined($_[0]->{object_class}) ? 1 : 0;
112             }
113              
114             sub object_class {
115 23     23 0 89 return $_[0]->{object_class};
116             }
117              
118             sub for_each_property {
119 1     1 0 2 my ($self, $code) = @_;
120              
121 1 50       3 return unless $self->is_object;
122              
123 1         3 my $object_class = $self->object_class;
124              
125 1         6 my $all_classes = mro::get_linear_isa($object_class);
126              
127 1         6 for (my $i = scalar(@$all_classes) - 1; $i >= 0; $i--) {
128 3         7 my $class = $all_classes->[$i];
129 3         4 my $properties = $object_properties{$class};
130 3 100       10 next unless $properties;
131              
132 2         7 map { $code->($_, $properties->{$_}) } keys %$properties;
  2         6  
133             }
134              
135             }
136              
137             sub properties {
138 1     1 0 2 my ($self) = @_;
139              
140 1 50       3 return {} unless $self->is_object;
141              
142 1         3 my $ret = {};
143             $self->for_each_property(sub {
144 2     2   12 $ret->{$_[0]} = $_[1];
145 1         8 });
146 1         5 return $ret;
147             }
148              
149             sub property_type {
150 12     12 0 21 my ($self, $property_name) = @_;
151              
152 12 50       22 return undef unless $self->is_object;
153              
154 12         20 my $want_array = wantarray;
155              
156 12         32 my $object_class = $self->object_class;
157              
158 12         74 my $all_classes = mro::get_linear_isa($object_class);
159              
160 12         42 for (my $i = 0; $i < scalar(@$all_classes); $i++) {
161 25         31 my $class = $all_classes->[$i];
162 25         36 my $properties = $object_properties{$class};
163 25 100       145 next unless $properties;
164              
165 9 100       20 if ($want_array) {
166 7 100       31 return $properties->{$property_name}, $class if defined($properties->{$property_name});
167             }
168             else {
169 2 100       15 return $properties->{$property_name} if defined($properties->{$property_name});
170             }
171             }
172              
173             # If we fall out here then no parent class has the
174             # property we're looking for.
175 10         76 return undef;
176             }
177              
178             # Arrays and maps are not yet supported
179             sub is_array {
180 0     0 0 0 return 0;
181             }
182             sub is_map {
183 0     0 0 0 return 0;
184             }
185             sub inner_type {
186 0     0 0 0 return undef;
187             }
188              
189             sub coerce_in {
190 10 50   10 0 22 if (@_ == 2) {
191 0         0 return $_[0]->{coerce_in}->($_[1]);
192             }
193             else {
194 10         30 return $_[0]->{coerce_in};
195             }
196             }
197              
198             sub coerce_out {
199 10 50   10 0 39 if (@_ == 2) {
200 0         0 return $_[0]->{coerce_out}->($_[1]);
201             }
202             else {
203 10         34 return $_[0]->{coerce_out};
204             }
205             }
206              
207             sub compare {
208 0     0 0 0 my ($a, $b, $reversed) = @_;
209              
210             # Fast path for our singleton primitive types
211 0 0       0 return 0 if $a == $b;
212              
213 0 0 0     0 return -1 unless $a->isa('Util::ObjectThing::Type') && $b->isa('Util::ObjectThing::Type');
214              
215 0 0 0     0 if ($a->is_object && $b->is_object) {
216 0         0 return $a->object_class cmp $b->object_class;
217             }
218              
219 0 0 0     0 if ($a->is_array && $b->is_array) {
220 0         0 return $a->inner_type <=> $b->inner_type;
221             }
222              
223 0 0 0     0 if ($a->is_map && $b->is_map) {
224 0         0 return $a->inner_type <=> $b->inner_type;
225             }
226              
227 0         0 return -1;
228              
229             }
230              
231             sub _register_object_property {
232 10     10   22 my ($class, $object_class, $property_name, $type) = @_;
233              
234 10   100     60 $object_properties{$object_class} ||= {};
235 10         5267 $object_properties{$object_class}{$property_name} = $type;
236             }
237              
238             1;