File Coverage

blib/lib/POOF/Properties.pm
Criterion Covered Total %
statement 175 219 79.9
branch 59 122 48.3
condition 12 22 54.5
subroutine 24 29 82.7
pod 0 3 0.0
total 270 395 68.3


line stmt bran cond sub pod time code
1             package POOF::Properties;
2              
3 4     4   23992 use 5.007;
  4         14  
  4         166  
4 4     4   20 use strict;
  4         9  
  4         123  
5 4     4   18 use warnings;
  4         13  
  4         128  
6              
7 4     4   20 use Carp qw(croak confess);
  4         8  
  4         275  
8 4     4   2380 use Class::ISA;
  4         9081  
  4         116  
9              
10 4     4   3916 use POOF::DataType;
  4         53  
  4         252  
11              
12             our $VERSION = '1.0';
13              
14 4         235 use constant ACCESSLEVEL =>
15             {
16             'Private' => 0,
17             'Protected' => 1,
18             'Public' => 2,
19 4     4   34 };
  4         7  
20              
21 4     4   21 use constant PUBLIC => '@@__POOF::Properties::Public__@@';
  4         23  
  4         155  
22 4     4   19 use constant DUMMY => '@@__POOF::Properties::DUMMY__@@';
  4         5  
  4         15855  
23              
24             my $GROUPS;
25              
26             our $DEBUG = 0;
27              
28             # CONSTRUCTOR
29             sub TIEHASH
30             {
31 8     8   1289 my $class = shift;
32 8         21 my $obj = {};
33 8         18 bless $obj, $class;
34 8         116 $obj->_init(@_);
35 8         29 return $obj;
36             }
37              
38             #-------------------------------------------------------------------------------
39             # Protected Methods go here
40              
41             sub _init
42             {
43 8     8   15 my ($obj,$args,$self,$exceptionHandlerRef,$groupHandlerRef,$propertiesRef) = @_;
44            
45 8 50       52 $obj->{'self'} =
46             $self
47             ? $self
48             : ref($obj);
49            
50 8 50       34 $obj->{'exceptionHandler'} = $exceptionHandlerRef
51             if $exceptionHandlerRef;
52            
53 8         14 $GROUPS = $groupHandlerRef;
54            
55 8         24 $$propertiesRef->{ $obj->{'self'} } = $obj;
56              
57 8         27 $obj->_initializeHash;
58              
59             # let's setup the property definitions
60 8         19 my @defs =
61             ref $args eq 'ARRAY'
62 8 0       34 ? @{$args}
    50          
63             : ref $args eq 'HASH'
64             ? ($args)
65             : undef;
66            
67 8         29 $obj->_buildDispatch(@defs);
68            
69 8         23 return $args;
70             }
71              
72             sub _buildDispatch
73             {
74 8     8   12 my $obj = shift;
75 8         19 my @definitions = @_;
76            
77             # create the dispatch table for each class context
78 8         16 my $class = $obj->{'self'};
79            
80             # ancestors don't have any visibility into the child
81             # child can see ancestors public and protected properties
82             # child can only override virtual properties of it's ancestors
83 8         28 $obj->{'dispatch'}->{$class} = { };
84              
85 8         18 my $dispatch = $obj->{'dispatch'}->{$class};
86            
87 8         16 foreach my $def (@definitions)
88             {
89             # make sure all keys are lower case
90 30         37 %{$def} = map { lc($_) => $def->{ $_ } } keys %{$def};
  30         226  
  165         382  
  30         83  
91            
92             # let's grab the stuff
93 30         136 my ($name,$data,$datadef,$access,$definer,$virtual) = @$def{ qw(name data datadef access class virtual) };
94            
95             # default to 0 on virtual
96 30   100     103 $virtual ||= 0;
97            
98             # make sure the values are lower case when applicable
99 30         55 $access = ucfirst(lc($access));
100            
101             # if not access was defined we'll default to public
102 30 50       104 $access =
    0          
    50          
103             $access
104             ? exists ACCESSLEVEL->{ $access }
105             ? ACCESSLEVEL->{ $access }
106             : confess "Unkown access type: $access"
107             : $name eq DUMMY
108             ? ACCESSLEVEL->{'Private'}
109             : ACCESSLEVEL->{'Public'};
110            
111             # complain if there is no valid POOF::DataTypes object in the definition
112 30 50       92 confess "There is an invalid data object in this definition\n"
113             unless $obj->_relationship($data,'POOF::DataType') =~ /^(?:self|child)$/;
114            
115             # take care of illegal redefinitions of non-virtuals
116 30 0 33     91 confess qq|Illegal attempt to redefined the non-virtual property "$name" in class "$dispatch->{ $name }->{'class'}" by "$definer"\n|
      33        
117             if
118             (
119             exists $dispatch->{ $name }
120             && $dispatch->{ $name }->{'virtual'} != 1
121             && $dispatch->{ $name }->{'access'} != 0
122             );
123            
124             # handle group stuff
125             # first remove this property from all groups for this class
126 30         34 foreach my $group (keys %{$$GROUPS->{ $class }})
  30         102  
127             {
128 2         8 @{$$GROUPS->{ $class }->{ $group }} =
  3         7  
129             (
130             grep
131             {
132 2         5 $_ ne $name
133             }
134 2         3 @{$$GROUPS->{ $class }->{ $group }}
135             );
136             }
137            
138 30         50 foreach my $group (@{$datadef->{'groups'}})
  30         76  
139             {
140 3 100       12 $$GROUPS->{ $class }->{ $group } = []
141             unless exists $$GROUPS->{ $class }->{ $group };
142            
143             # only add it the first time it's seen and this should keep the right order
144 3 50       8 unless (grep { $name eq $_ } @{$$GROUPS->{ $class }->{ $group }})
  3         9  
  3         10  
145             {
146 3         5 push (@{$$GROUPS->{ $class }->{ $group }},$name)
  3         11  
147             }
148             }
149            
150 0         0 my ($i0,$i1,$i2) =
151             $access == 0
152             ? exists $dispatch->{ $definer }->{ $name }
153 0         0 ? @{$dispatch->{ $definer }->{ $name }}{ qw(index0 index1 index2) }
154             : ()
155             : exists $dispatch->{ $name }
156 30 50       98 ? @{$dispatch->{ $name }}{ qw(index0 index1 index2) }
    50          
    100          
157             : ();
158            
159             # handling the private caller context (basically anything that made it this far
160             # should be in the context as it should be accesible from self
161 30 50       42 if ($i0)
162             {
163             # we are redefining a property
164 0         0 $obj->{'key'}->[0]->[$i0] = $name;
165 0         0 $obj->{'val'}->[0]->[$i0] = $data;
166             }
167             else
168             {
169             # new property
170 30         36 push(@{ $obj->{'key'}->[0] }, $name);
  30         68  
171 30         38 push(@{ $obj->{'val'}->[0] }, $data);
  30         62  
172            
173             # grabbing the index value to store with prop in dispatch
174 30         34 $i0 = $#{ $obj->{'key'}->[0] };
  30         66  
175             }
176            
177             # handling the protected caller context
178 30 100       67 if ($access > 0)
179             {
180 26 50       36 if ($i1)
181             {
182             # we are redefining a property
183 0         0 $obj->{'key'}->[1]->[$i1] = $name;
184 0         0 $obj->{'val'}->[1]->[$i1] = $data;
185             }
186             else
187             {
188             # new property
189 26         26 push(@{ $obj->{'key'}->[1] }, $name);
  26         59  
190 26         31 push(@{ $obj->{'val'}->[1] }, $data);
  26         50  
191            
192             # grabbing the index value to store with prop in dispatch
193 26         27 $i1 = $#{ $obj->{'key'}->[1] };
  26         42  
194             }
195             }
196            
197             # handling the public caller context
198 30 100       62 if ($access > 1)
199             {
200 26 50       38 if ($i2)
201             {
202             # we are redefining a property
203 0         0 $obj->{'key'}->[2]->[$i2] = $name;
204 0         0 $obj->{'val'}->[2]->[$i2] = $data;
205             }
206             else
207             {
208             # new property
209 26         26 push(@{ $obj->{'key'}->[2] }, $name);
  26         53  
210 26         27 push(@{ $obj->{'val'}->[2] }, $data);
  26         49  
211            
212             # grabbing the index value to store with prop in dispatch
213 26         32 $i2 = $#{ $obj->{'key'}->[2] };
  26         43  
214             }
215             }
216              
217             # finally we can add the property to this class context index
218 30 100       55 if ($access == 0)
219             {
220 4         51 $obj->{'dispatch'}->{ $definer }->{ $name } =
221             {
222             'class' => $definer,
223             'name' => $name,
224             'access' => $access,
225             'datadef' => $datadef,
226             'data' => $data,
227             'virtual' => $virtual,
228             'index0' => $i0,
229             'index1' => $i1,
230             'index2' => $i2,
231             };
232             }
233             else
234             {
235 26         224 $dispatch->{ $name } =
236             {
237             'class' => $definer,
238             'name' => $name,
239             'access' => $access,
240             'datadef' => $datadef,
241             'data' => $data,
242             'virtual' => $virtual,
243             'index0' => $i0,
244             'index1' => $i1,
245             'index2' => $i2,
246             };
247             }
248             }
249             }
250              
251             #-------------------------------------------------------------------------------
252             # property definitions
253             sub _dispatch
254             {
255 65     65   86 my ($obj,$k) = @_;
256            
257 65         122 my $callerContext = $obj->_callerContext;
258 65         239 my $caller = (caller(1))[0];
259 65         154 my $self = $obj->{'self'};
260            
261             # ugly hack that needs to be fix
262 65 50       142 defined $caller && $caller =~ s/POOF::TEMPORARYNAMESPACE//o;
263            
264 65 0       164 my $dispatch =
    0          
    0          
    50          
265             $callerContext < 0
266             ? # caller is parent. Parent can access it's privates
267             # plus public and protected from child
268             exists $obj->{'dispatch'}->{ $caller }->{ $k }
269             ? # caller has a private with this name let's give it to it
270             $obj->{'dispatch'}->{ $caller }
271             : # caller does not have a private with this name let's see if
272             # we have a property with this name
273             exists $obj->{'dispatch'}->{ $self }->{ $k }
274             ? # let's see if the property is not private
275             $obj->{'dispatch'}->{ $self }->{ $k }->{'access'} > 0
276             ? # property is not private let's give it to caller
277             $obj->{'dispatch'}->{ $self }
278             : # property is private so let's not give him anything
279             { }
280             : # self does not have what caller is looking for, just give
281             # back self context and we'll give access violation below
282             $obj->{'dispatch'}->{ $self }
283             : # caller is not parent so normal rules apply, just get dispatch
284             # for self and control access below
285             $obj->{'dispatch'}->{ $self };
286            
287             # thow an exception if the property does not exist
288 65 50       158 confess qq|Property "$k" does not exist|
289             unless exists $dispatch->{ $k };
290            
291             # thow an exception if the caller cannot access the property
292 65 50       164 confess "Access violation"
293             unless $dispatch->{ $k }->{'access'} >= $callerContext;
294            
295 65         162 return $dispatch;
296             }
297              
298              
299              
300             sub Definition
301             {
302 0     0 0 0 my ($obj,$k) = @_;
303 0         0 my $p = $obj->_dispatch($k)->{ $k };
304              
305             return
306             {
307 0 0       0 'min' => $p->{'data'}->min,
308             'max' => $p->{'data'}->max,
309             'size' => $p->{'data'}->size,
310             'maxsize' => $p->{'data'}->maxsize,
311             'minsize' => $p->{'data'}->minsize,
312             'null' => $p->{'data'}->null,
313             'default' => $p->{'data'}->default,
314             'ptype' => $p->{'data'}->ptype,
315             'otype' => $p->{'data'}->otype,
316             'type' => $p->{'data'}->type,
317             'format' => $p->{'data'}->format,
318             'orm' => $p->{'data'}->orm,
319             'regex' => $p->{'data'}->regex,
320             'options' => $p->{'data'}->type eq 'enum' ? $p->{'data'}->options : [],
321             };
322             }
323              
324              
325             sub EnumOptions
326             {
327 0     0 0 0 my ($obj,$k) = @_;
328 0         0 my $p = $obj->_dispatch($k)->{ $k };
329            
330             return
331 0 0       0 $p->{'data'}->type eq 'enum'
332             ? $p->{'data'}->options
333             : confess "Property is not of enum type and has no options";
334            
335             }
336              
337             #-------------------------------------------------------------------------------
338             # hash functionality bindings
339             sub CLEAR
340 2     2   769 {
341             # my $obj = shift;
342             # my $accessContext = $obj->_accessContext;
343            
344             # clean is simply going to undef the values of the
345             # properties that are withing the scope of the access context
346             #croak "Properties cannot be deleted at runtime";
347             }
348              
349             sub EXISTS
350             {
351 22     22   1250 my ($obj,$k) = @_;
352            
353 22         42 my $callerContext = $obj->_callerContext;
354 22         113 my $caller = (caller(0))[0];
355            
356             # ugly hack that needs to be fix
357 22 50       63 defined $caller && $caller =~ s/POOF::TEMPORARYNAMESPACE//o;
358            
359 22 0       76 my $dispatch =
    50          
360             $callerContext < 0
361             ? exists $obj->{'dispatch'}->{ $caller }->{ $k }
362             ? $obj->{'dispatch'}->{ $caller }
363             : { }
364             : $obj->{'dispatch'}->{ $obj->{'self'} };
365            
366             return
367 22 50 33     188 exists $dispatch->{ $k }
368             && $dispatch->{ $k }->{'access'} >= $callerContext
369             ? 1
370             : undef;
371             }
372              
373              
374             sub FETCH
375             {
376 43     43   2536 my ($obj,$k) = @_;
377 43         93 my $p = $obj->_dispatch($k)->{ $k };
378            
379 43         72 my $d = $p->{'data'};
380 43         124 my $v = $d->value;
381            
382             # let's apply the ifilter if defined
383 43 50 66     299 if (defined $d->ofilter && ref($d->ofilter) eq 'CODE')
384             {
385             eval
386 0         0 {
387 0         0 $v = &{$d->ofilter}($obj->{'___refobj___'},$v);
  0         0  
388             };
389 0 0       0 if ($@)
390             {
391             # generate error
392 0 0       0 &{$obj->{'exceptionHandler'}}
  0         0  
393             (
394             $obj->{'___refobj___'},
395             $k,
396             {
397             'code' => 172,
398             'description' => $@,
399             'value' => $v
400             }
401             ) if defined $obj->{'exceptionHandler'};
402 0         0 return;
403             }
404             }
405            
406 43         170 return $v;
407             }
408              
409             sub DELETE
410             {
411 1     1   683 my ($obj,$k) = @_;
412 1         292 confess "Properties cannot be deleted at runtime";
413             }
414              
415             sub STORE
416             {
417 27     27   2647 my ($obj,$k,$v) = @_;
418            
419 27 100       155 if ($k eq '___refobj___')
420             {
421 5         10 $obj->{$k} = $v;
422 5         14 return;
423             };
424            
425 22         49 my $p = $obj->_dispatch($k)->{ $k };
426 22         33 my $d = $p->{'data'};
427            
428             # let's apply the ifilter if defined
429 22 50 66     74 if (defined $d->ifilter && ref $d->ifilter eq 'CODE')
430             {
431             eval
432 0         0 {
433 0         0 $v = &{$d->ifilter}($obj->{'___refobj___'},$v)
  0         0  
434             };
435 0 0       0 if ($@)
436             {
437             # generate error
438 0 0       0 &{$obj->{'exceptionHandler'}}
  0         0  
439             (
440             $obj->{'___refobj___'},
441             $k,
442             {
443             'code' => 171,
444             'description' => $@,
445             'value' => $v
446             }
447             ) if defined $obj->{'exceptionHandler'};
448 0         0 return;
449             }
450             }
451            
452 22         70 $d->value( $v );
453            
454             # handle any possible errors
455 22 100       74 if ($d->pErrors)
456             {
457 2 50       16 &{$obj->{'exceptionHandler'}}($obj->{'___refobj___'},$k,$d->pGetErrors->{'value'})
  2         9  
458             if defined $obj->{'exceptionHandler'};
459              
460 2         26 return;
461             }
462             else
463             {
464 20 50       90 &{$obj->{'exceptionHandler'}}($obj->{'___refobj___'},$k)
  20         60  
465             if defined $obj->{'exceptionHandler'};
466            
467 20         216 return $v;
468             }
469             }
470            
471             sub FIRSTKEY
472             {
473 9     9   618 my ($obj) = @_;
474 9         42 my $caller = (caller(0))[0];
475 9         22 my $callerContext = $obj->_callerContext(1);
476            
477             # ugly hack that needs to be fix
478 9 50       22 defined $caller && $caller =~ s/POOF::TEMPORARYNAMESPACE//o;
479              
480             # the FIRSTKEY and NEXTKEY functions will return different stuff depending
481             # on access. If it is called in a private context than any key can be
482             # returned, however if it is not in private context, then only the keys
483             # to public properties can be returned.
484            
485 9         33 $obj->{'cnt'}->{ $caller } = 0;
486 9         22 return $obj->_getNextKey($caller,$callerContext);
487             }
488              
489             sub NEXTKEY
490             {
491 45     45   59 my ($obj) = @_;
492 45         225 my $k = $obj->_getNextKey((caller(0))[0],$obj->_callerContext(1));
493 45 100       136 return unless defined $k;
494 36         115 return $k;
495             }
496              
497             sub _getNextKey
498             {
499 54     54   77 my ($obj,$caller,$callerContext) = @_;
500 54 100       117 my $access = $callerContext > 0 ? $callerContext : 0;
501            
502             # ugly hack that needs to be fix
503 54 50       99 defined $caller && $caller =~ s/POOF::TEMPORARYNAMESPACE//o;
504            
505 54         48 my $k;
506 54         87 while( $obj->{'cnt'}->{ $caller } <= $#{ $obj->{'key'}->[ $access ] } )
  54         152  
507             {
508 45         105 my $pk = $obj->{'key'}->[ $access ]->[ $obj->{'cnt'}->{ $caller }++ ];
509            
510 45 0       108 my $dispatch =
    50          
511             $callerContext < 0
512             ? exists $obj->{'dispatch'}->{ $caller }->{ $pk }
513             ? $obj->{'dispatch'}->{ $caller }
514             : { }
515             : $obj->{'dispatch'}->{ $obj->{'self'} };
516            
517 45 50 33     225 if (exists $dispatch->{ $pk } && $dispatch->{ $pk }->{'access'} >= $callerContext)
518             {
519 45         46 $k = $pk;
520 45         65 last;
521             }
522             }
523            
524 54         118 return $k;
525             }
526              
527            
528             #-------------------------------------------------------------------------------
529             # private Methods
530              
531             sub Trace
532             {
533 0     0 0 0 my $obj = shift;
534 0         0 my %caller;
535 0         0 @caller{ qw(
536             0-package
537             1-filename
538             2-line
539             3-subr
540             4-has_args
541             5-wantarray
542             6-evaltext
543             7-is_required
544             8-hints
545             9-bitmask
546             ) } = caller(1);
547            
548 0         0 warn "$caller{'3-subr'}\n\t\tcalled from line [ $caller{'2-line'} ] in ($caller{'0-package'}) $caller{'1-filename'}\n";
549             }
550              
551             sub _dumpAccessContext
552             {
553 0     0   0 my $obj = shift;
554 0         0 my $start = 0;
555 0         0 my %caller;
556              
557 0         0 for($start .. 5)
558             {
559 0         0 @caller{ qw(
560             0-package
561             1-filename
562             2-line
563             3-subr
564             4-has_args
565             5-wantarray
566             6-evaltext
567             7-is_required
568             8-hints
569             9-bitmask
570             ) } = caller($_);
571              
572 0 0       0 last unless defined $caller{'0-package'};
573            
574 0         0 warn "\ncaller $_\n" . "-"x50 . "\n";
575 0         0 $obj->_dumpCaller(\%caller);
576             }
577             }
578              
579             sub _dumpCaller
580             {
581 0     0   0 my $obj = shift;
582 0         0 my $caller = shift;
583 0 0       0 warn "\n" . (
584             join "\n", map
585             {
586 0         0 sprintf "\t%-15s = %-15s", $_,
587             defined $caller->{$_}
588             ? $caller->{$_}
589             : 'undef'
590             } sort keys %$caller) . "\n\n";
591             }
592              
593             sub _callerContext
594             {
595 141     141   199 my ($obj,$level) = @_;
596 141   100     832 my $caller = (caller($level || 2))[0];
597            
598             # ugly hack that needs to be fix
599 141 100       388 defined $caller && $caller =~ s/POOF::TEMPORARYNAMESPACE//o;
600            
601 141         322 my $relationship = $obj->_relationship($caller,$obj->{'self'});
602            
603             return
604 141 50       489 $relationship eq 'self'
    50          
    100          
605             ? 0 # 'private'
606             : $relationship eq 'child'
607             ? 1 # 'protected'
608             : $relationship eq 'parent'
609             ? -1 # parent has not visibility into children
610             : 2 # 'public';
611            
612             }
613              
614             sub _relationship
615             {
616 171     171   210 my $obj = shift;
617 171 100       223 my ($class1,$class2) = map { $_ ? ref $_ ? ref $_ : $_ : '' } @_;
  342 100       1027  
618              
619 171 100       599 return 'self' if $class1 eq $class2;
620              
621 81         199 my %family1 = map { $_ => 1 } Class::ISA::super_path( $class1 );
  152         3293  
622 81         352 my %family2 = map { $_ => 1 } Class::ISA::super_path( $class2 );
  3         125  
623              
624             return
625 81 50       1573 exists $family1{ $class2 }
    50          
626             ? 'child'
627             : exists $family2{ $class1 }
628             ? 'parent'
629             : 'unrelated';
630             }
631              
632              
633             sub _initializeHash
634             {
635 8     8   18 my ($obj) = @_;
636             }
637            
638              
639             1;
640             __END__