File Coverage

blib/lib/Class/Core.pm
Criterion Covered Total %
statement 25 64 39.0
branch 0 12 0.0
condition n/a
subroutine 9 13 69.2
pod n/a
total 34 89 38.2


line stmt bran cond sub pod time code
1             # Class::Core Wrapper System
2             # Version 0.03 alpha 2/26/2013
3             # Copyright (C) 2013 David Helkowski
4              
5             # This program is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as
7             # published by the Free Software Foundation; either version 2 of the
8             # License, or (at your option) any later version. You may also can
9             # redistribute it and/or modify it under the terms of the Perl
10             # Artistic License.
11            
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16              
17             =head1 NAME
18              
19             Class::Core - Class wrapper system providing parameter typing, logging, and class auto-instanitation
20              
21             =head1 VERSION
22              
23             0.04
24              
25             =cut
26              
27             # The container that is wrapped around an object for remote calling
28             package Class::Core::VIRTCALL;
29 1     1   1160 use strict;
  1         2  
  1         39  
30 1     1   7 use Carp;
  1         2  
  1         79  
31             #use Data::Dumper;
32 1     1   1526 use XML::Bare qw/xval forcearray/;
  1         21225  
  1         101  
33 1     1   10 use Data::Dumper;
  1         2  
  1         562  
34             our $AUTOLOAD;
35              
36 0     0     sub DESTROY { Class::Core::VIRT::DESTROY( @_ ); }
37              
38             sub AUTOLOAD {
39 0     0     my $virt = shift;
40 0           my $tocall = $AUTOLOAD;
41 0           $tocall =~ s/^Class::Core::VIRTCALL:://;
42            
43 0           my $obj = $virt->{'obj'};
44 0           my $map = $obj->{'_map'};
45 0           my $ref = $map->{ $tocall }; # grab the function reference if there is one ( if not is a virtual call )
46 0           my $cls = $obj->{'_class'};
47 0           my $spec = $obj->{'_spec'};
48 0 0         if( ( scalar @_ ) % 2 ) { confess "Non even list - $cls->$tocall\n"; }
  0            
49 0           my %parms = @_;
50            
51             # Skipping spec checking on remote calls, for now - TODO
52            
53 0 0         my $inner = { parms => \%parms, _funcspec => ($spec ? $spec->{'funcs'}{$tocall} : 0), virt => $virt };#_glob => $obj->{'_glob'},
54 0           bless $inner, "Class::Core::INNER";
55 0           my $callback = $obj->{'_callback'};
56            
57 0           my $okay = 1;
58 0 0         if( $callback ) {
59             # inner contains call parameters
60             # virt is the virtual wrapper around the object
61 0           $okay = &$callback( $inner, $virt, $tocall, \%parms );
62             }
63 0 0         if( !$okay ) {
64 0           die "Call to $tocall in $cls failed due to callback\n";
65             }
66            
67 0           my %noremote = (
68             #'_duplicate' => 1
69             );
70 0 0         if( $noremote{ $tocall } ) {
71 0           $inner->{'ret'} = &$ref( $inner, $virt ); # call these functions directlhy
72             }
73             else {
74            
75 0           print "Attempt to call remote function $tocall on class $cls\n";
76 0           my $xml = Class::Core::_hash2xml( \%parms );
77 0           print "Args:$xml\n";
78            
79 0           my $callfunc = $virt->{'_callfunc'};
80 0           my $app = $obj->{'_app'};
81 0           my $call = $virt->{'_call'};
82 0           $inner->{'ret'} = $callfunc->( $app, $call, $tocall, $xml );
83            
84             # Skipping checking of return value - TODO
85             }
86            
87 0 0         return $inner->{'res'} ? $inner : $inner->{'ret'};
88             }
89              
90             sub _duplicate {
91 0     0     my $virt = shift;
92 0           my $obj = $virt->{'obj'};
93             #print "Duplicating ".$obj->{'_class'}."\n";
94 0           my $newvirt = { obj => $obj, src => $virt, @_ };
95 0           return bless $newvirt, 'Class::Core::VIRTCALL';
96             }
97              
98             sub _hasfunc {
99 0     0     my ( $ob, $tocall ) = @_;
100             #my $spec = $ob->{'obj'}{'_spec'};
101             #return $spec->{'funcs'}{ $tocall };
102 0           return $ob->{'obj'}{'_map'}{ $tocall };
103             }
104              
105             # The container that is wrapped around the object
106             package Class::Core::VIRT;
107 1     1   6 use strict;
  1         2  
  1         41  
108 1     1   6 use Carp;
  1         2  
  1         80  
109             #use Data::Dumper;
110 1     1   5 use XML::Bare qw/xval forcearray/;
  1         2  
  1         49  
111 1     1   5 use Data::Dumper;
  1         2  
  1         73  
112             our $AUTOLOAD;
113 1     1   1103 use threads;
  0            
  0            
114              
115             sub DESTROY {
116             my $virt = shift;
117             my $obj = $virt->{'obj'};
118             my $map = $obj->{'_map'};
119             my $cls = $obj->{'_class'};
120             #my $thr = threads->self();
121             #my $tid = $thr->tid();
122             #return if( $tid ); # this is only required really if we are using ithreads ( eg: win32 )
123             if( $virt->{'src'} ) {
124             #print "Attempting to destroy request copy an object of type $cls\n";
125             }
126             else {
127             #print "Attempting to destroy an object of type $cls\n";
128             }
129             } # If this is not defined, AUTOLOAD gets called for it and creates problems ( on Win32 at any rate )
130              
131             sub AUTOLOAD {
132             my $virt = shift;
133             my $tocall = $AUTOLOAD;
134             $tocall =~ s/^Class::Core::VIRT:://;
135            
136             my $obj = $virt->{'obj'};
137             my $map = $obj->{'_map'};
138             my $ref = $map->{ $tocall }; # grab the function reference
139             my $cls = $obj->{'_class'};
140             if( !$ref ) {
141             confess "No function $tocall in $cls\n";
142             }
143             my $spec = $obj->{'_spec'};
144             my $pcount = ( scalar @_ );
145             my $x = 0;
146             my %parms;
147             if( $pcount % 2 ) {
148             if( $pcount != 1 ) {
149             confess "Non even list - $cls->$tocall\n";
150             }
151             else {
152             $x = $_[0];
153             }
154             }
155             else {
156             %parms = @_;
157             }
158            
159             my $allerr = '';
160             my $fspec;
161             if( $spec ) {
162             $fspec = $spec->{'funcs'}{ $tocall };
163             if( $fspec ) { # if the function has specs; make sure one passes
164             if( $fspec->{'sig'} ) {
165             # Additionally check global specs if they are set
166             if( $fspec->{'in'} || $fspec->{'out'} || $fspec->{'ret'} ) {
167             my $err = _checkspec( $obj, $fspec, \%parms );
168             $allerr .= $err if( $err );
169             }
170            
171             my $sigs = forcearray( $fspec->{'sig'} );
172             my $ok = 0;
173             for my $sig ( @$sigs ) {
174             my $err = _checkspec( $virt, $sig, \%parms );
175             if( $err ) {
176             $allerr .= "$err\n";
177             }
178             else {
179             $ok = 1;
180             if( $sig->{'set'} ) {
181             my $sets = forcearray( $sig->{'set'} );
182             for my $set ( @$sets ) {
183             my $name = xval $set->{'name'};
184             my $val = xval $set->{'val'};
185             print "Setting $name to $val\n";
186             }
187             }
188             last;
189             }
190             }
191             }
192             else {
193             $allerr .= _checkspec( $obj, $fspec, \%parms );
194             }
195             }
196             }
197            
198             die $allerr if( $allerr );
199            
200             my $inner = { parms => \%parms, _funcspec => $fspec, virt => $virt };#_glob => $obj->{'_glob'},
201             bless $inner, "Class::Core::INNER";
202             my $callback = $obj->{'_callback'};
203             my $calldone = $obj->{'_calldone'};
204             if( $callback && !$calldone ) { die "wtf"; }
205             if( !$callback && $calldone ) { die "wtf"; }
206            
207             my $okay = 1;
208             my $callid = 0;
209             if( $callback ) {
210             # inner contains call parameters
211             # virt is the virtual wrapper around the object
212            
213             $okay = &$callback( $inner, $virt, $tocall, \%parms, \$callid );
214             }
215             if( !$okay ) {
216             #die "Call to $tocall in $cls failed due to callback\n";
217             if( $calldone ) {
218             &$calldone( $inner, $virt, $tocall, \%parms, $callid );
219             }
220             return 0;
221             }
222            
223             my $rval = $inner->{'ret'} = &$ref( $inner, $virt, $x ); # call the function
224             if( $spec ) {
225             my $retspec = $spec->{'ret'};
226             if( $retspec && %$retspec ) {
227             my $type = $retspec->{'type'};
228             my $err = _checkval( $retspec, $type, $rval );
229             die "While checking return - $err" if( $err );
230             }
231             }
232            
233             if( $calldone ) {
234             &$calldone( $inner, $virt, $tocall, \%parms, $callid );
235             }
236            
237             return $inner->{'res'} ? $inner : $inner->{'ret'};
238             }
239              
240             sub get_source {
241             my ( $ob ) = @_;
242             return $ob->{'src'};
243             }
244              
245             sub _hasfunc {
246             my ( $ob, $tocall ) = @_;
247             #my $spec = $ob->{'obj'}{'_spec'};
248             #return $spec->{'funcs'}{ $tocall };
249             return $ob->{'obj'}{'_map'}{ $tocall };
250             }
251              
252             sub _checkspec {
253             my ( $obj, $spec, $parms ) = @_;
254             my $state = $spec->{'state'};
255             if( $state && $state ne $obj->{'_state'} ) {
256             _tostate( $obj, $state );
257             }
258             my $ins = $spec->{'in'};
259             for my $key ( keys %$ins ) {
260             my $in = $ins->{ $key };
261             my $type = $in->{'type'};
262             my $val = $parms->{ $key };
263             my $err = _checkval( $in, $type, $val );
264             return "While checking $key - $err" if( $err );
265             }
266             return 0;
267             }
268              
269             sub _tostate {
270             my ( $obj, $dest ) = @_;
271             print "Attempt to change to state $dest\n";
272             $obj->{'_map'}{'init_'.$dest}->();
273             $obj->{'_state'} = $dest;
274             }
275              
276             sub _checkval {
277             my ( $node, $type, $val ) = @_;
278             my $xml = $node->{'xml'};
279             if( ! defined $val ) {
280             if( $xml->{'optional'} ) { return 0; }
281             #my @arr = caller;
282             return "not defined and should be a $type";
283             }
284             my $err = 'undefined';
285            
286             if( $type eq 'number' ) { $err = _checknum( $node, $val ); }
287             if( $type eq 'bool' ) { $err = _checkbool( $node, $val ); }
288             if( $type eq 'path' ) { $err = _checkpath( $node, $val ); }
289             if( $type eq 'hash' ) { $err = _checkhash( $node, $val ); }
290             return $err;
291             }
292              
293             # Note that the 'hash' type could refer to another 'hash' type.
294             # This will not actually cause loops even if referring to the same hash, because
295             # a different inset set of specs will be followed. If the spec is changed to take use
296             # of 'shared' signatures that can be checked in multiple functions, then loops could occur.
297             sub _checkhash {
298             my ( $node, $val ) = @_;
299             my $spec = $node->{'xml'};
300             if( $spec->{'sig'} ) {
301             my $sigs = forcearray( $spec->{'sig'} );
302             my $allerr = '';
303             my $ok = 0;
304             for my $sig ( @$sigs ) {
305             # Note that the first parameter to the following function is set to 0. This is ob.
306             # This is needed to be able to change the state of ob if needed based on the spec.
307             # When checking a hash, a hash does not need to change the state so this doesn't matter.
308             # Note that bad things will happen if you set the 'state' attribute on a hash signature.
309             # Don't do that.
310             my $err = _checkspec( 0, $sig, $val );
311             if( $err ) {
312             $allerr .= "$err\n";
313             }
314             else {
315             $ok = 1;
316             # We are going to still allow setting of variables within a hash. This is likely overkill, and
317             # this code should probably be removed. Leaving it for now for parallelism.
318             if( $sig->{'set'} ) {
319             my $sets = forcearray( $sig->{'set'} );
320             for my $set ( @$sets ) {
321             my $name = xval $set->{'name'};
322             my $val = xval $set->{'val'};
323             print "Setting $name to $val\n";
324             }
325             }
326             last;
327             }
328             }
329             if( !$ok ) {
330             return $allerr;
331             }
332             }
333             else {
334             my $err = _checkspec( 0, $spec, $val );
335             return $err if( $err );
336             }
337             return 0;
338             }
339              
340             sub _checkpath {
341             my ( $in, $val ) = @_;
342             my $clean = $val;
343             $clean =~ s|//+|/|g;
344             if( $clean ne $val ) { return "Path contains // - Path is \"$val\""; }
345             $clean =~ s/[:?*+%<>|]//g;
346             if( $clean ne $val ) { return "Path contains one of the following ':?*+\%<>|' - Path is \"$val\""; }
347             my $xml = $in->{'xml'};
348             if ( $xml->{'isdir' } && ! -d $clean ) { return "Dir does not exist - \"$clean\""; }
349             elsif( $xml->{'isfile'} && ! -f $clean ) { return "File does not exist - \"$clean\""; }
350             elsif( $xml->{'exists'} && ! -e $clean ) { return "Path does not exist - \"$clean\""; }
351             }
352              
353             sub _checkbool {
354             my ( $in, $val ) = @_;
355             {
356             no warnings 'numeric';
357             if( ($val+0 ne $val) || ( $val != 0 && $val != 1 ) ) {
358             return "not a boolean ( it is $val )";
359             }
360             }
361             return 0;
362             }
363              
364             sub _checknum {
365             my ( $in, $val ) = @_;
366             {
367             no warnings 'numeric';
368             if( $val*1 ne $val ) {
369             return "not a number ( it is \"$val\" )";
370             }
371             }
372             my $xml = $in->{'xml'};
373             if( $xml->{'min'} ) {
374             my $min = xval $xml->{'min'};
375             if( $val < $min ) {
376             return "less than the allowed minimum of $min ( it is $val )";
377             }
378             }
379             if( $xml->{'max'} ) {
380             my $max = xval $xml->{'max'};
381             if( $val > $max ) {
382             return "more than the allowed maxmimum of $max ( it is $val )";
383             }
384             }
385             return 0;
386             }
387              
388             sub _duplicate {
389             my $virt = shift;
390             my $obj = $virt->{'obj'};
391             #print "Duplicating ".$obj->{'_class'}."\n";
392             my $newvirt = { obj => $obj, src => $virt, @_ };
393             return bless $newvirt, 'Class::Core::VIRT';
394             }
395              
396             # Parameter input and output container
397             package Class::Core::INNER;
398             use strict;
399             use Data::Dumper;
400             use Carp;
401              
402             our $AUTOLOAD;
403              
404             sub AUTOLOAD {
405             my $virt = shift;
406             my $tocall = $AUTOLOAD;
407             #print "Tocall: $tocall\n";
408             if( $tocall =~ s/^Class::Core::INNER::// ) {
409             my $extend;
410             #print "******** Virt call to $tocall\n";
411             #$Data::Dumper::Maxdepth = 2;
412             $virt = $virt->{'virt'};
413             #print Dumper( $virt );
414             if( $extend = $virt->{'_extend'} ) {
415             return $extend->$tocall( $virt, @_ );
416             }
417             confess "No extension - $tocall";
418             }
419             }
420             sub DESTROY {
421             }
422             sub get {
423             my ( $inner, $name ) = @_;
424             return $inner->{'parms'}{ $name };
425             }
426             sub get_all {
427             my $inner = shift;
428             return $inner->{'parms'};
429             }
430             sub set {
431             my ( $inner, $name, $val ) = @_;
432             $inner->{'res'}{ $name } = $val;
433             }
434             sub get_res {
435             my ( $inner, $name ) = @_;
436             return $inner->{'res'}{ $name } || undef;
437             }
438             sub get_all_res {
439             my ( $inner, $name ) = @_;
440             return $inner->{'res'};
441             }
442              
443             # get an array of items
444             sub get_arr {
445             my ( $inner ) = shift;
446             my @ret;
447             for my $key ( @_ ) {
448             push( @ret, $inner->{'parms'}{ $key } );
449             }
450             return @ret;
451             }
452             sub add {
453             my ( $inner, $name, $val ) = @_;
454            
455             my $spec = $inner->{'_funcspec'};
456             my $outs = $spec->{'out'};
457             #print Dumper( $self );
458             my $outspec = $outs->{ $name };
459            
460             if( $outspec ) {
461             my $type = $outspec->{'type'};
462             my $err = Class::Core::VIRT::_checkval( $outspec, $type, $val );
463             die "While checking $name - $err" if( $err );
464             }
465             $inner->{'parms'}{$name} = $val;
466             }
467              
468             package Class::Core;
469             use strict;
470             use Data::Dumper;
471             use XML::Bare qw/xval forcearray/;
472             use vars qw/@EXPORT_OK @EXPORT @ISA %EXPORT_TAGS $VERSION/;
473             require Exporter;
474             @ISA = qw(Exporter);
475             @EXPORT = qw//;
476             @EXPORT_OK = qw/new/;
477             %EXPORT_TAGS = ( all => [ qw/new/ ] );
478             $VERSION = '0.04';
479              
480             sub read_spec {
481             my ( $func ) = @_;
482             my ( %in, %out, %ret );
483             my $func_spec = { in => \%in, out => \%out, ret => \%ret, x => $func };
484            
485             my $ins = forcearray( $func->{'in'} );
486             for my $in ( @$ins ) {
487             my $name = xval $in->{'name'};
488             my $type = xval $in->{'type'}, 'any';
489             $in{ $name } = { type => $type, xml => $in };
490             }
491            
492             my $outs = forcearray( $func->{'out'} );
493             for my $out ( @$outs ) {
494             my $name = xval $out->{'name'};
495             my $type = xval $out->{'type'}, 'any';
496             $out{ $name } = { type => $type, xml => $out };
497             }
498            
499             my $ret_x = $func->{'ret'};
500             if( $ret_x ) {
501             my $type = xval $ret_x->{'type'};
502             $ret{'type'} = $type;
503             $ret{'xml'} = $ret_x;
504             }
505            
506             if( $func->{'state'} ) {
507             my $state = xval $func->{'state'};
508             $func_spec->{'state'} = $state;
509             }
510            
511             if( $func->{'set'} ) {
512             $func_spec->{'set'} = $func->{'set'};
513             }
514            
515             if( $func->{'perms'} ) {
516             my @arr = split(',', $func->{'perms'}{'value'} );
517             $func_spec->{'perms'} = \@arr;
518             }
519            
520             return $func_spec;
521             }
522              
523             sub create_object {
524             my ( $class, $objin ) = @_;
525             no strict 'refs';
526            
527             my $map = {};
528             my %obj = ( _class => $class, _state => '_loaded', _map => $map, %$objin );
529             my $glob = $obj{'_glob'};
530            
531             my $classes = $glob->{'classes'};
532            
533             # Read in the specification setup for the passed class
534             my $spectext = ${"$class\::spec"} || 'file';
535             if( $spectext ) {
536             my ( $ob, $xml );
537             if( $spectext eq 'file' ) {
538             my $file = $class;
539             $file =~ s|::|/|g;
540             my $pm_xml = $INC{ "$file.pm" } . ".xml";
541             ( $ob, $xml ) = new XML::Bare( file => $pm_xml );
542             }
543             else {
544             ( $ob, $xml ) = new XML::Bare( text => $spectext );
545             }
546             my $func_specs = {};
547             my %spec = ( funcs => $func_specs );
548             $obj{'_spec'} = \%spec;
549             $obj{'_specx'} = $xml;
550             my $funcs = forcearray( $xml->{'func'} );
551             for my $func ( @$funcs ) {
552             my $name = xval $func->{'name'};
553            
554             my $func_spec;
555             if( $func->{'sig'} ) {
556             my $sigs = forcearray( $func->{'sig'} );
557             my @func_specs = ();
558             for my $sig ( @$sigs ) {
559             push( @func_specs, read_spec( $sig ) );
560             }
561             $func_spec = { sig => \@func_specs };
562             }
563             else {
564             $func_spec = read_spec( $func );
565             }
566             $func_specs->{ $name } = $func_spec;
567             }
568             }
569            
570             # Create duplicates of all functions in the source class
571             my $ref = \%{"$class\::"};
572            
573             #print "$class:\n";
574             for my $key ( keys %$ref ) {
575             next if( $key =~ m/^(new|import|DESTROY|BEGIN)$/ );
576             #print " $key\n";
577             my $func_ref = \&{"$class\::$key"};
578             my $fname = $key;
579             $key =~ s/^$class\:://;
580             $map->{ $fname } = $func_ref;
581             }
582            
583             return \%obj;
584             }
585             my %obj_store;
586             sub new {
587             my $class = shift; # this is the name of the class; eg: Module::test
588             no strict 'refs';
589            
590             my %hashin = ( @_ );
591            
592             my $objin = $hashin{'obj'} || {};
593             my $glob = ( $objin->{'_glob'} ||= { objs => {} } );
594             my $objs = $glob->{'objs'};
595            
596             my $obj;
597             if( ( $obj = $objs->{ $class } ) ) {
598             }
599             else {
600             $obj = $objs->{ $class } = create_object( $class, $objin );
601             }
602            
603             # Create the virtual wrapper
604             my %hash = ( %hashin, obj => $obj );
605             my $hashref = \%hash;
606             if( $hashin{'_call'} ) {
607             bless $hashref, 'Class::Core::VIRTCALL';
608             }
609             else {
610             bless $hashref, 'Class::Core::VIRT';
611             }
612             # push( @{ $obj->{'insts'} }, $hashref ); perhaps we want to store the instance
613            
614             # Call the constructor if one exists
615             my $map = $obj->{'_map'};
616             if( $map->{'construct'} ) { $hashref->construct(); }
617            
618             return $hashref;
619             }
620              
621             sub _hash2xml {
622             my ( $node, $name ) = @_;
623             my $ref = ref( $node );
624             return if( $name && $name =~ m/^\_/ );
625             my $txt = $name ? "<$name>" : '';
626             if( $ref eq 'ARRAY' ) {
627             $txt = '';
628             for my $sub ( @$node ) {
629             $txt .= _hash2xml( $sub, $name );
630             }
631             return $txt;
632             }
633             elsif( $ref eq 'HASH' ) {
634             for my $key ( keys %$node ) {
635             $txt .= _hash2xml( $node->{ $key }, $key );
636             }
637             }
638             else {
639             $node ||= '';
640             if( $node =~ /[<]/ ) { $txt .= ''; }
641             else { $txt .= $node; }
642             }
643             if( $name ) {
644             $txt .= "";
645             }
646            
647             return $txt;
648             }
649              
650             1;
651              
652             __END__