File Coverage

lib/Class/Dot/Meta/Property.pm
Criterion Covered Total %
statement 138 145 95.1
branch 41 50 82.0
condition 13 15 86.6
subroutine 21 21 100.0
pod 0 6 0.0
total 213 237 89.8


line stmt bran cond sub pod time code
1             # $Id$
2             # $Source$
3             # $Author$
4             # $HeadURL$
5             # $Revision$
6             # $Date$
7             package Class::Dot::Meta::Property;
8              
9 16     16   83 use strict;
  16         27  
  16         571  
10 16     16   86 use warnings;
  16         51  
  16         470  
11 16     16   76 use version;
  16         1396  
  16         98  
12 16     16   3966 use 5.00600;
  16         1445  
  16         2128  
13              
14 16     16   78 use Carp qw(carp croak confess);
  16         29  
  16         2708  
15 16     16   97 use Params::Util qw(_ARRAYLIKE _HASHLIKE);
  16         1230  
  16         829  
16 16     16   83 use Class::Plugin::Util qw(require_class);
  16         38  
  16         103  
17 16     16   764 use Scalar::Util qw(blessed);
  16         26  
  16         695  
18              
19 16     16   78 use Class::Dot::Registry;
  16         24  
  16         823  
20             our $REGISTRY = Class::Dot::Registry->new();
21              
22 16         109 use Class::Dot::Meta::Type qw(
23             _NEWSCHOOL_TYPE
24             _OLDSCHOOL_TYPE
25 16     16   28361 );
  16         40  
26 16         71 use Class::Dot::Meta::Method qw(
27             install_sub_from_coderef
28             install_sub_from_class
29 16     16   101 );
  16         33  
30 16     16   9932 use Class::Dot::Meta::Accessor;
  16         52  
  16         780  
31              
32 16     16   102 use Class::Dot::Devel::Sub::Name;
  16         30  
  16         71  
33              
34             our $VERSION = qv('2.0.0_15');
35             our $AUTHORITY = 'cpan:ASKSH';
36              
37             my $ATTR_EXISTS = 1;
38             my $ATTR_EXISTS_CACHED = 2;
39              
40             my $DEFAULT_ACCESSOR_TYPE = 'Overrideable';
41              
42             my $TYPE_PRIVACY_DEFAULT = 'public';
43              
44             my %TYPE_PRIVACY_ALIASES = (
45             'rw' => 'public',
46             'ro' => 'readonly',
47             'wo' => 'writeonly',
48             'xx' => 'private',
49             );
50              
51             my %TYPE_PRIVACY_RULES = (
52             public => {
53             has_getter => 1,
54             has_setter => 1,
55             },
56             readonly => {
57             has_getter => 1,
58             },
59             writeonly => {
60             has_setter => 1,
61             },
62             private => {
63             # nil
64             },
65             );
66              
67             # ------------------------------ CONSTRUCTOR ----------------------------- #
68             sub new {
69 173     173 0 255 my ($class, $options_ref) = @_;
70 173   50     768 $options_ref ||= { };
71              
72 173         218 return bless { %{$options_ref} }, $class;
  173         1021  
73             }
74              
75             # ------------------------------ METHODS --------------------------------- #
76              
77             sub traverse_isa_for_property {
78 114     114 0 199 my ($self, $the_class, $attr) = @_;
79 114 100       236 my $class = ref $the_class ? ref $the_class
80             : $the_class;
81 114         288 my $metaclass = $REGISTRY->get_metaclass_for($class);
82              
83 114         233 my $has_property;
84 114         245 my $all_properties = { };
85              
86 114         529 my $isa = $metaclass->get_linear_isa($class);
87              
88 114 50       141 if (scalar @{ $isa } > 1) {
  114         284  
89 114         218 ISA:
90 114         130 for my $isa (@{ $isa }) {
91 208         585 my $class_meta = $REGISTRY->get_meta_for($isa);
92 208 100       379 if (defined $attr) {
93 103 100       272 if (exists $class_meta->{$attr}) {
94 42         66 $has_property = $class_meta->{$attr};
95 42         250 last ISA;
96             }
97             }
98             else {
99 713         1916 PROPERTY:
100 105         127 while (my ($name, $val) = each %{ $class_meta }) {
101             # we always use the first property we get, since that
102             # matches the method resolution order, so we skip the
103             # property if we already have it.
104 608 50       1028 if (!exists $all_properties->{$name}) {
105 608         1335 $all_properties->{$name} = $val;
106             }
107             }
108             }
109             }
110             }
111             else {
112 0         0 my $class_meta = $REGISTRY->get_meta_for($class);
113 0 0       0 if (defined $attr) {
114 0         0 $has_property = exists $class_meta->{$attr};
115             }
116             else {
117 0         0 $all_properties = {%{ $class_meta }};
  0         0  
118             }
119             }
120              
121 114 100       688 return defined $attr ? $has_property
122             : $all_properties;
123             }
124              
125             sub properties_for_class {
126 49     49 0 79 my ($self, $the_class) = @_;
127 49 100       141 my $class = ref $the_class ? ref $the_class
128             : $the_class;
129              
130 49         187 my $isa_cache = $REGISTRY->get_isa_cache_for($class);
131 49 100       130 if ($isa_cache) {
132 2 100       9 if ($ENV{TESTING_CLASS_DOT}) {
133 1         4 $isa_cache->{__is_retrieved_cached__}++;
134             }
135 2         9 return $isa_cache;
136             }
137              
138 47         146 return $self->traverse_isa_for_property($class);
139             }
140              
141             sub composites_for {
142 3     3 0 7 my ($self, $class, $name, $composite) = @_;
143            
144 3 50       17 if (!require_class($composite)) {
145 0         0 croak "Couldn't load composite class '$composite'\n";
146             }
147              
148 3         88 my $object_init = Class::Dot::Typemap->get_type('Object');
149 3         14 return $self->define_property(
150             $name, $object_init->($composite, auto => 1)
151             => $class
152             );
153             }
154              
155             sub _merge_hash_left_precedent {
156 93     93   165 my ($left_side, $right_side) = @_;
157 93   100     209 $left_side ||= { };
158 93   50     192 $right_side ||= { };
159              
160 93         104 my $res = {%{ $left_side }};
  93         303  
161 93         402 while (my ($key, $value) = each %{ $right_side }) {
  601         1491  
162 508 50       931 if (!exists $left_side->{$key}) {
163 508         1074 $res->{$key} = $value;
164             }
165             }
166              
167 93         169 return $res;
168             }
169              
170             sub define_property {
171 93     93 0 177 my ($self, $property, $isa, $caller_class, $options) = @_;
172 93         140 my $accessors = { };
173              
174             # Can't add properties to finalized classes.
175             #confess "Can't add new properties to finalized class $caller_class!"
176             # if $REGISTRY->is_finalized($caller_class);
177              
178             # ### Merge context and class wide options.
179 93         286 my $class_options = $REGISTRY->get_options_for(
180             $caller_class
181             );
182 93         221 my $all_options = _merge_hash_left_precedent($options, $class_options);
183              
184             # ## # Create a type instance for the type if it isn't one already.
185 93 100       247 if (! _NEWSCHOOL_TYPE($isa)) {
186 6         43 my $any_type = Class::Dot::Typemap->get_type('Any');
187             # The current value becomes the default_value of the type.
188             # e.g
189             # property who => "the quick brown fox"
190             # becomes a type instance of type Any and a default
191             # value of "the quick brown fox".
192 6         18 $isa = $any_type->($isa);
193             }
194              
195             # Decide the accessor type.
196 93         117 my $accessor_type;
197 93 100       466 if (exists $all_options->{'-accessor_type'}) {
    100          
    50          
198 6         13 $accessor_type = $all_options->{'-accessor_type'};
199             }
200             elsif ($all_options->{'-chained'}) {
201 6         9 $accessor_type = 'Chained';
202             }
203             elsif ($all_options->{'-constrained'}) {
204 0         0 $accessor_type = 'Constrained';
205             }
206             else {
207 81         117 $accessor_type = $DEFAULT_ACCESSOR_TYPE;
208             }
209 93         293 $isa->{accessor_type} = $accessor_type;
210              
211 93         472 my $accessor_gen = Class::Dot::Meta::Accessor->new({
212             type => $accessor_type
213             });
214              
215             # Get the privacy rules for this privacy setting.
216 93         212 my $privacy_rules;
217 93         147 my $privacy_type = $all_options->{privacy};
218 93         213 ($privacy_rules, $privacy_type)
219             = $self->get_privacy_rule($privacy_type);
220 93         175 $isa->{privacy} = $privacy_type;
221 93         152 $isa->{privacy_rule} = $privacy_rules;
222              
223 93   100     561 my $is_mutator = (
224             ! $all_options->{'-getter_prefix'}
225             && ! $all_options->{'-setter_prefix'}
226             );
227              
228 93 100       198 if ($is_mutator) {
229 12         46 $accessors->{$property} = $accessor_gen->create_mutator(
230             $caller_class, $property, $isa, $all_options, $privacy_rules
231             );
232             }
233              
234             # ### Create get accessor.
235 93 100 100     438 if (!$is_mutator && $privacy_rules->{has_getter}) {
236 76         147 my $get_property = $all_options->{'-getter_prefix'} . $property;
237 76         283 $isa->{getter_name} = $get_property;
238 76         249 $accessors->{$get_property} = $accessor_gen->create_get_accessor(
239             $caller_class, $property, $isa, $all_options, $privacy_rules
240             );
241             }
242              
243             # ### Create set accessor.
244              
245 93 100 100     438 if (!$is_mutator && $privacy_rules->{has_setter}) {
246 75         141 my $set_property = $all_options->{'-setter_prefix'} . $property;
247              
248             # Keep preceeding _'s. E.g __private becomes __set_private
249             # instead of set__private.
250 75 100       223 if ($property =~ /^(_+)/xms) {
251 3         6 my $uscores = $1;
252 3         4 $set_property = $property;
253 3         8 $set_property =~ s/^_+//xms;
254 3         7 $set_property
255             = $uscores.$all_options->{'-setter_prefix'}.$set_property;
256             }
257              
258             # Store the names inside the type instance for later use.
259 75         148 $isa->{setter_name} = $set_property;
260              
261 75         277 $accessors->{$set_property} = $accessor_gen->create_set_accessor(
262             $caller_class, $property, $isa, $all_options, $privacy_rules
263             );
264             }
265              
266             # ### Install accessors
267 16     16   116 no strict 'refs'; ## no critic
  16         34  
  16         5177  
268 93         141 while (my ($accessor_name, $accessor_coderef) = each %{ $accessors }) {
  256         769  
269 163 100       159 if (not *{ "$caller_class\::$accessor_name" }{CODE}) {
  163         813  
270 161         452 install_sub_from_coderef(
271             $accessor_coderef => $caller_class, $accessor_name
272             );
273             }
274             }
275              
276             # ### Save metadata.
277 93         319 my $class_meta = $REGISTRY->get_meta_for($caller_class);
278 93         1013 $class_meta->{$property} = $isa;
279              
280 93         779 return;
281             }
282              
283             sub get_privacy_rule {
284 93     93 0 270 my ($self, $opt_privacy_type) = @_;
285 93 100       196 my $privacy_type = defined $opt_privacy_type ? $opt_privacy_type
286             : $TYPE_PRIVACY_DEFAULT;
287              
288             # Decide which accessors to create based on the privacy option.
289 93 100       429 if (exists $TYPE_PRIVACY_ALIASES{$privacy_type}) {
290 17         36 $privacy_type = $TYPE_PRIVACY_ALIASES{$privacy_type};
291             }
292              
293 93 50       227 confess "Unknown attribute privacy type: $privacy_type"
294             if not exists $TYPE_PRIVACY_RULES{$privacy_type};
295              
296 93         128 my $rules = $TYPE_PRIVACY_RULES{$privacy_type};
297              
298 93 50       311 return wantarray ? ($rules, $privacy_type)
299             : $rules;
300             }
301              
302             1;
303              
304             __END__