File Coverage

blib/lib/DDC/PP/Object.pm
Criterion Covered Total %
statement 123 143 86.0
branch 37 60 61.6
condition 29 51 56.8
subroutine 27 32 84.3
pod 0 21 0.0
total 216 307 70.3


line stmt bran cond sub pod time code
1             ##-*- Mode: CPerl -*-
2              
3             ##======================================================================
4             package DDC::PP::Object;
5 20     20   11990 use JSON;
  20         227140  
  20         114  
6 20     20   2986 use Carp qw(carp confess);
  20         52  
  20         988  
7 20     20   114 use strict;
  20         57  
  20         977  
8              
9             ##======================================================================
10             ## debugging & wrapping utilities
11              
12             ## undef = $CLASS->nomethod($method_name)
13             ## + defines a method $CLASS::$method_name which just throws an error
14             sub nomethod {
15 100     100 0 219 my ($class,$method_name) = @_;
16 100         246 my $method = "${class}::${method_name}";
17 20     20   110 no strict "refs";
  20         41  
  20         1900  
18             *$method = sub {
19 0     0   0 confess("${method}(): method not implemented");
20 100         723 };
21             }
22              
23             ## undef = $CLASS->defprop($property)
24             ## + defines $CLASS::get$Property and $CLASS::set$Property methods
25             sub defprop {
26 1200     1200 0 2304 my ($class,$prop)=@_;
27 1200         2515 my $getmethod = "${class}::get".ucfirst($prop);
28 1200         1943 my $setmethod = "${class}::set".ucfirst($prop);
29 20     20   112 no strict 'refs';
  20         41  
  20         2293  
30 1200     2362   6892 *$getmethod = sub { return $_[0]{$prop}; };
  2362         14367  
31 1200     638   7987 *$setmethod = sub { return $_[0]{$prop}=$_[1]; };
  638         7356  
32             }
33              
34             ## undef = $CLASS->defalias($propertyFrom,$propertyTo, $doGet=1, $doSet=1)
35             ## + aliases $CLASS::get$PropertyFrom and $CLASS::set$PropertyFrom methods to $CLASS::get$PropertyTo etc.
36             sub defalias {
37 160     160 0 345 my ($class,$pfrom,$pto, $doGet,$doSet)=@_;
38 160         386 my $getmethod = "${class}::get".ucfirst($pfrom);
39 160         275 my $setmethod = "${class}::set".ucfirst($pfrom);
40 20     20   112 no strict 'refs';
  20         44  
  20         15812  
41 160 50 33     725 *$getmethod = $class->can('get'.ucfirst($pto)) if (!defined($doGet) || $doGet);
42 160 50 33     1834 *$setmethod = $class->can('set'.ucfirst($pto)) if (!defined($doSet) || $doSet);
43             }
44              
45             ##======================================================================
46             ## xs replacements
47              
48             sub new {
49 863     863 0 1774 my $that = shift;
50 863   33     6468 return bless { @_ }, ref($that)||$that;
51             }
52              
53             __PACKAGE__->nomethod('DumpTree');
54             __PACKAGE__->nomethod('refcnt');
55             __PACKAGE__->nomethod('self');
56             __PACKAGE__->nomethod('free');
57              
58             #__PACKAGE__->nomethod('Children');
59             # + override this if order is important (e.g. for DiaCollo CQWith, CQAnd, etc.)
60             sub Children {
61 8 50   8 0 19 return UNIVERSAL::isa($_[0],'HASH') ? [grep {UNIVERSAL::isa($_,'DDC::PP::Object')} values %{$_[0]}] : [];
  40         76  
  8         21  
62             }
63              
64             #__PACKAGE__->nomethod('Descendants');
65             sub Descendants {
66 2     2 0 19 my @stack = (shift);
67 2         4 my %visited = qw();
68 2         5 my @kids = qw();
69 2         3 my ($obj);
70 2         6 while (@stack) {
71 14         19 $obj = shift(@stack);
72 14 50       43 next if (exists $visited{$obj});
73 14         21 push(@kids,$obj);
74 14         23 $visited{$obj} = undef;
75 14 50       26 unshift(@stack, @{$obj->Children}) if (ref($obj));
  14         44  
76             }
77 2         9 return \@kids;
78             }
79              
80             #__PACKAGE__->nomethod('DisownChildren');
81             sub DisownChildren {
82 0     0 0 0 my $obj = shift;
83 0 0       0 return if (!ref($obj));
84 0         0 delete @$obj{$obj->members};
85             }
86              
87             #__PACKAGE__->nomethod('toString');
88             sub toString {
89 0     0 0 0 return "$_[0]";
90             }
91              
92             sub toJson {
93 156     156 0 1769 return JSON::to_json( $_[0], {utf8=>1,pretty=>0,canonical=>1,allow_blessed=>1,convert_blessed=>1} );
94             }
95              
96             ##-- json utils
97             sub jsonClass {
98 196   33 196 0 865 (my $class = ref($_[0]) || $_[0]) =~ s/^DDC::PP:://;
99 196         575 return $class;
100             }
101              
102              
103             ##======================================================================
104             ## Traversal
105              
106             ##--------------------------------------------------------------
107             ## $obj = $obj->mapTraverse(\&CODE)
108             ## + calls \&CODE on $obj and each DDC::PP::Object descendant in turn
109             ## + \&CODE is called as \&CODE->($obj), and should return a new value for the corresponding slot
110             ## + object tree is traversed in depth-first visit-last order
111             sub mapTraverse {
112 10     10 0 80 my ($obj,$code) = @_;
113 10         47 return $obj->mapVisit($obj,$code);
114             }
115              
116             ## $oldval = CLASS->mapVisit($curval, \$code)
117             sub mapVisit {
118 134     134 0 182 my ($that,$nod,$code) = @_;
119 134 100 100     492 if (#UNIVERSAL::isa($nod,'DDC::PP::Object') ##-- breaks DDC::Any
    100 66        
    50 33        
120             ref($nod) && UNIVERSAL::can($nod,'members')
121             ) {
122 10         19 my ($oldval,$newval);
123 10         17 foreach my $slot (grep {$nod->can("get$_")} $nod->members) {
  80         254  
124 80         200 $oldval = $nod->can("get${slot}")->($nod);
125 80         140 $newval = $that->mapVisit($oldval, $code);
126 80 100 66     509 $nod->can("set${slot}")->($nod,$newval) if ((defined($newval) && defined($oldval) && $newval ne $oldval)
      100        
      100        
      66        
127             || defined($newval)
128             || defined($oldval));
129             }
130 10         35 return $code->($nod);
131             }
132             elsif (ref($nod) && UNIVERSAL::isa($nod,'ARRAY')) {
133 32         50 my $newval = [grep {defined($_)} map {$that->mapVisit($_,$code)} @$nod];
  44         80  
  44         72  
134 32 50       69 return ref($newval) eq 'ARRAY' ? $newval : bless($newval, ref($nod));
135             }
136             elsif (ref($nod) && UNIVERSAL::isa($nod,'HASH')) {
137 0         0 my $newval = {map {($_=>$that->mapVisit($nod->{$_},$code))} keys %$nod};
  0         0  
138 0 0       0 return ref($newval) eq 'HASH' ? $newval : bless($newval, ref($nod));
139             }
140 92         141 return $nod;
141             }
142              
143              
144             ##======================================================================
145             ## C->Perl
146              
147             ##--------------------------------------------------------------
148             ## \%hash = $obj->toHash(%opts)
149             ## + %opts:
150             ## (
151             ## trimClassNames => $bool, ##-- auto-trim class-names?
152             ## json => $bool, ##-- for JSON-ification?
153             ## )
154             ## + returns an object as a (nested) perl hash
155             ## + pure-perl variant just returns object
156             sub toHash {
157 296     296 0 1268 my ($obj,%opts) = @_;
158 296 0 33     614 return $obj if (!defined($obj) && !ref($obj));
159 296         446 my $class = ref($obj);
160 296 100 66     1930 $class =~ s/^DDC::(?:XS|PP|Any)::// if ($opts{trimClassNames} || $opts{json}); ##-- use toJson()-style class names
161             return {
162             (map {
163 2046         5792 ( $_ => $obj->valToPerl($obj->can("get$_")->($obj),%opts) )
164             } grep {
165 296         770 $obj->can("get$_")
  2046         5051  
166             } $obj->members),
167             class => $class,
168             };
169             }
170              
171             ##--------------------------------------------------------------
172             ## $perlval = $CLASS_OR_OBJECT->valToPerl($cval,%opts)
173             ## + %opts: as for toHash()
174             ## + returns a perl-encoded representation of $cval
175             sub valToPerl {
176 2246     2246 0 4049 my ($that,$cval,%opts) = @_;
177 2246 100       4875 if (!ref($cval)) {
    100          
    50          
    50          
178 1542         6974 return $cval;
179             } elsif (UNIVERSAL::can($cval,'toHash')) {
180 318         857 return $cval->toHash(%opts);
181             } elsif (UNIVERSAL::isa($cval,'HASH')) {
182 0         0 return {(map {($_=>$that->valToPerl($cval->{$_},%opts))} keys %$cval)};
  0         0  
183             } elsif (UNIVERSAL::isa($cval,'ARRAY')) {
184 386         1292 return [map {$that->valToPerl($_,%opts)} @$cval];
  200         444  
185             }
186 0         0 return $cval; ##-- CODE- or GLOB-ref?
187             }
188              
189              
190             ##--------------------------------------------------------------
191             ## @classes = $CLASS_OR_OBJ->inherits()
192             ## + returns a list of all classes from which $CLASS_OR_OBJ inherits
193             ## + called by toHash()
194             sub inherits {
195 20     20   168 no strict 'refs';
  20         40  
  20         1480  
196 5281     5281 0 6849 my $that = shift;
197 5281   66     11045 my $class = ref($that) || $that;
198 5281         6120 return ($class, map {inherits($_)} @{"${class}::ISA"});
  4975         7278  
  5281         15648  
199             }
200              
201             ##--------------------------------------------------------------
202             ## @keys = $CLASS_OR_OBJ->members()
203             ## + returns a list of all members with a "set${Key}" method supported by $CLASS_OR_OBJ or any superclasss
204             ## + called by toHash()
205             sub members {
206 20     20   124 no strict 'refs';
  20         40  
  20         12822  
207 306     306 0 600 my $that = shift;
208 306         423 my ($class,$symtab,%keys);
209 306         595 foreach $class ($that->inherits) {
210 5281         6767 $symtab = \%{"${class}::"};
  5281         10797  
211             @keys{(
212 7198         18006 grep {exists $symtab->{"set$_"}}
213 5281 100       17104 map { /^get([[:upper:]].*)$/ ? $1 : qw() }
  101510         157383  
214             keys %$symtab
215             )} = qw();
216             }
217 306         1302 return keys %keys;
218             }
219              
220             ##======================================================================
221             ## Perl->C-like
222              
223             ##--------------------------------------------------------------
224             ## $obj = CLASS->newFromHash(\%hash)
225             ## + creates a C++-like object from a (nested) perl hash
226             sub newFromHash {
227 6     6 0 14 my ($that,$hash) = @_;
228 6   33     23 my $class = ref($that) || $that;
229 6 50 33     35 return $hash if (!defined($hash) || UNIVERSAL::isa($hash,$class));
230 6 50       19 confess(__PACKAGE__ , "::newFromHash(): argument '$hash' is neither undef, a HASH-ref, nor an object of class $class")
231             if (!UNIVERSAL::isa($hash,'HASH'));
232              
233 6 50       18 $class = $hash->{class} if (defined($hash->{class}));
234 6 50       35 $class = "DDC::PP::$class" if ($class !~ /:/); ##-- honor toJson()-style class names
235 6 50       24 my $obj = $class->new()
236             or confess(__PACKAGE__, "::newFromHash(): $class->new() failed");
237              
238 6         13 my ($key,$val,$valobj, $setsub);
239 6         26 while (($key,$val) = each %$hash) {
240 44 100       83 next if ($key eq 'class');
241              
242 38 50       182 if ( !($setsub = $obj->can("set".ucfirst($key))) ) {
243 0         0 warn(__PACKAGE__, "::newFromHash(): ignoring key '$key' for object of class '$class'");
244 0         0 next;
245             }
246 38         88 $valobj = $that->valFromPerl($val);
247 38         83 $setsub->($obj,$valobj);
248             }
249              
250 6         17 return $obj;
251             }
252              
253             ##--------------------------------------------------------------
254             ## $cval = $CLASS_OR_OBJECT->valFromPerl($perlval)
255             ## + returns a c-like representation of $perlval
256             sub valFromPerl {
257 40     40 0 99 my ($that,$pval) = @_;
258 40 100 66     135 if (!ref($pval)) {
    100          
    50          
    50          
259 22         48 return $pval;
260             } elsif (UNIVERSAL::isa($pval,'HASH') && $pval->{class}) {
261 4         21 return $that->newFromHash($pval);
262             } elsif (UNIVERSAL::isa($pval,'HASH')) {
263 0         0 return {(map {($_=>$that->valFromPerl($pval->{$_}))} keys %$pval)};
  0         0  
264             } elsif (UNIVERSAL::isa($pval,'ARRAY')) {
265 14         28 return [map {$that->valFromPerl($_)} @$pval];
  2         5  
266             }
267 0         0 return $pval; ##-- CODE- or GLOB-ref?
268             }
269              
270              
271             ##======================================================================
272             ## Clone
273              
274             ## $obj2 = $obj->clone()
275             sub clone {
276 0     0 0 0 return $_[0]->newFromHash($_[0]->toHash);
277             }
278              
279             ##======================================================================
280             ## JSON
281              
282             ##--------------------------------------------------------------
283             ## $obj = CLASS->newFromJson($json_string,%json_opts)
284             ## + creates a C++ object from a json string
285             sub newFromJson {
286 0     0 0 0 my ($that,$json,%opts) = @_;
287 0         0 my $hash = JSON::from_json($json, { utf8=>!utf8::is_utf8($json), relaxed=>1, allow_nonref=>1, %opts });
288 0         0 return $that->newFromHash($hash);
289             }
290              
291             ## $json = $obj->TO_JSON
292             sub TO_JSON {
293 160     160 0 4921 return $_[0]->toHash(json=>1);
294             }
295              
296              
297             1; ##-- be happy
298              
299             =pod
300              
301             =head1 NAME
302              
303             DDC::PP::Object - common perl base class for DDC::PP objects
304              
305             =head1 SYNOPSIS
306              
307             #-- Preliminaries
308             use DDC::PP;
309             $CLASS = 'DDC::PP::Object';
310            
311             ##---------------------------------------------------------------------
312             ## C -> Perl
313             $q = DDC::PP->parse("foo && bar");
314             $qs = $q->toString; ##-- $qs is "('foo' && 'bar')"
315             $hash = $q->toHash(); ##-- query encoded as perl hash-ref
316            
317             #... the perl object can be manipulated directly (perl refcounting applies)
318             $hash->{Dtr1} = {class=>'CQTokExact',Value=>'baz'}; ##-- NO memory leak!
319            
320             ##---------------------------------------------------------------------
321             ## Perl->C
322             $q2 = $CLASS->newFromHash($hash); ##-- $q2 needs explicit free()
323             $qs2 = $q2->toString(); ##-- $qs2 is "(@'baz' && 'bar')
324            
325             ##---------------------------------------------------------------------
326             ## Deep copy & Traversal
327            
328             $q3 = $q->clone(); ##-- wraps newFromHash($q->toHash)
329             $q = $q->mapTraverse(\&CODE); ##-- recursively tweak sub-objects
330            
331             ##---------------------------------------------------------------------
332             ## JSON utilities
333             $json = $q->toJson(); ##-- ddc-internal json-ification
334             $json = $q->TO_JSON(); ##-- wraps toHash() for the JSON module
335             $obj = $CLASS->newFromJson($str); ##-- wraps newFromHash(from_json($str))
336            
337             ##---------------------------------------------------------------------
338             ## Debugging
339             $obj->DumpTree(); ##-- dumps substructure to STDERR
340             $obj->free(); ##-- expplicit deep destruction, use at your own risk
341             \@kids = $obj->Children(); ##-- ARRAY-ref of direct children
342             \@desc = $obj->Descendants(); ##-- ARRAY-ref of descendants
343             undef = $obj->DisownChildren(); ##-- prevent deep destruction (dummy method; you should never need this)
344             $cnt = $obj->refcnt(); ##-- get internal reference count (dummy method)
345              
346              
347              
348             =head1 DESCRIPTION
349              
350             The DDC::PP::Object class is a pure-perl fork of the L class, which see.
351              
352              
353             =head1 SEE ALSO
354              
355             perl(1),
356             DDC::PP(3perl),
357             DDC::PP::CQuery(3perl),
358             DDC::PP::CQCount(3perl),
359             DDC::PP::CQFilter(3perl),
360             DDC::PP::CQueryOptions(3perl),
361             DDC::PP::CQueryCompiler(3perl).
362              
363             =head1 AUTHOR
364              
365             Bryan Jurish Emoocow@cpan.orgE
366              
367             =head1 COPYRIGHT AND LICENSE
368              
369             Copyright (C) 2016 by Bryan Jurish
370              
371             This library is free software; you can redistribute it and/or modify
372             it under the same terms as Perl itself, either Perl version 5.14.2 or,
373             at your option, any later version of Perl 5 you may have available.
374              
375             =cut
376