File Coverage

blib/lib/Class/Structured.pm
Criterion Covered Total %
statement 15 136 11.0
branch 0 44 0.0
condition 0 20 0.0
subroutine 5 18 27.7
pod 8 8 100.0
total 28 226 12.3


line stmt bran cond sub pod time code
1             #!/usr/local/bin/perl
2            
3             =head1 NAME
4            
5             Class::Structured - provides a more structured class system for Perl
6            
7             =head1 DESCRIPTION
8            
9             Specifically, this function provides for variables with access specifiers
10             that will inherit properly, for constructors, and for abstract functions.
11            
12             Abstract functions may be used on their own with no performance penalty.
13            
14             Constructors and access specified variables each imply the use of the other -
15             and will incur a semi-significant performance penalty.
16            
17             Also, note that when using all of the features it can cause problems to define
18             an AUTOLOAD function - so please don't.
19            
20             =head1 HISTORY
21            
22             =over 2
23            
24             =item *
25            
26             02/04/02 - Robby Walker - released - version 0.1
27            
28             =item *
29            
30             12/10/01 - Robby Walker - added private variable support, tested - version 0.003
31            
32             =item *
33            
34             12/06/01 - Robby Walker - adding abstract listing, checking and constructors - version 0.002
35            
36             =item *
37            
38             12/05/01 - Robby Walker - created the file, wrote abstract support - version 0.001
39            
40             =back
41            
42             =head1 METHODS
43            
44             =over 4
45            
46             =cut
47             #----------------------------------------------------------
48            
49             package Class::Structured;
50            
51             # MODULE METADATA
52             our $VERSION = 0.1;
53             our @ISA = qw(Exporter);
54            
55             our @EXPORT = ();
56             our @EXPORT_OK = qw(declare_abstract implementation constructor default_constructor define_variables);
57             our %EXPORT_TAGS = (
58             all => [qw(declare_abstract implementation constructor default_constructor define_variables)]
59             );
60            
61             # PRAGMATIC DEPENDENCIES
62 1     1   108958 use strict "vars";
  1         4  
  1         40  
63 1     1   5 use strict "subs";
  1         2  
  1         24  
64 1     1   5 use warnings;
  1         6  
  1         31  
65            
66             # OUTSIDE DEPENDENCIES
67 1     1   5 use Carp;
  1         8  
  1         69  
68 1     1   978 use Set::Scalar;
  1         47263  
  1         2247  
69            
70             # ========================================================================
71             # METHODS
72             # ========================================================================
73            
74             # ------------------------------------------------------------------------
75             # Methods for abstract functions
76             # ------------------------------------------------------------------------
77            
78             =item declare_abstract
79            
80             Declares an abstract function in the current package.
81            
82             =cut
83             sub declare_abstract {
84 0     0 1   my $function_name = pop; # get last param as function name
85 0           my $package = caller;
86            
87             # update the abstract list (keep it as a weird name so we don't have a collision with a real variable name)
88 0           my $list_name = $package.'::'.'!structured!.abstracts';
89            
90 0 0         ${ $list_name } = Set::Scalar->new() unless defined ${ $list_name };
  0            
  0            
91 0           ${ $list_name }->insert( $function_name );
  0            
92            
93             # declare the function
94 0           *{ $package.'::'.$function_name } =
95             sub {
96 0     0     croak "$function_name in class $package is declared abstract, and cannot be called";
97 0           };
98             }
99            
100             =item list_abstracts
101            
102             Provides a list of all the abstracts left by a package for subclasses to implement.
103            
104             =cut
105             sub list_abstracts {
106 0     0 1   my $package = shift;
107            
108             # create a set to list all abstracts
109 0           my $plist_name = $package.'::!structured!.abstracts';
110 0           my $list;
111            
112             # add all locally declared abstracts - as definites
113 0 0         if ( defined ${ $plist_name } ) {
  0            
114 0           $list = ${ $plist_name }->clone;
  0            
115             } else {
116 0           $list = Set::Scalar->new;
117             }
118            
119             # get a set for each parent class's abstracts
120 0           my %parents;
121             my $parent;
122 0           my @parents = @{ $package.'::ISA' };
  0            
123 0           foreach $parent ( @parents ) {
124 0           my @abstracts = list_abstracts($parent);
125            
126 0 0         if ( @abstracts + 0 ) {
127 0           $parents{$parent} = Set::Scalar->new(@abstracts);
128             }
129             }
130            
131             # this variable holds a list of functions we know to be implemented (i.e. not abstract)
132 0           my $notlist = Set::Scalar->new;
133            
134             # now, step over each parent, adding abstracts when no other parent implements that function
135             # note that this code makes no allowance for AUTOLOAD, which is why we state earlier that this
136             # Perl feature should be avoided when using Class::Structured
137 0           foreach $parent (keys %parents) {
138 0           my $function;
139 0           my @abstracts = $parents{$parent}->members;
140            
141 0           foreach $function (@abstracts) {
142             # skip this if we already know the function to be abstract or implemented
143 0 0 0       next if ($list->member($function) || $notlist->member($function));
144            
145 0           my $can;
146 0 0         if ( defined *{ $package.'::'.$function }{CODE} ) {
  0            
147             # does this package override it?
148 0           $can = 1;
149             } else {
150             # does one of this package's parents override it
151 0           my $other;
152 0           $can = 0;
153 0           foreach $other (@parents) {
154 0 0         next if ($other eq $parent);
155            
156             # if the parent can run the function, and not just because it
157             # declares it abstract, mark the function as implemented
158 0 0 0       if ( !((exists $parents{$other}) && ($parents{$other}->member($function)))
      0        
159             && $other->can( $function ) )
160             {
161 0           $can = 1;
162 0           last;
163             }
164             }
165             }
166            
167             # add to the appropriate list
168 0 0         ($can ? $notlist : $list)->insert( $function );
169             }
170             }
171            
172 0           my @members = $list->members;
173 0           return @members;
174             }
175            
176             =item check_abstracts
177            
178             When instantiating a class, make sure that it has declared all the necessary abstracts
179            
180             =cut
181             sub check_abstracts {
182 0     0 1   my $package = shift;
183            
184             # if we have no abstracts, we are OK
185 0           return ! ( list_abstracts($package) + 0 );
186             }
187            
188             # ------------------------------------------------------------------------
189             # Constructor related functions
190             # ------------------------------------------------------------------------
191            
192             =item constructor
193            
194             Creates a new constructor.
195            
196             =cut
197             sub constructor {
198 0     0 1   my $name = shift;
199            
200             # load parameters, doing some aerobics to ensure their proper loading
201 0   0 0     my $code = pop || sub {};
  0            
202 0 0         my %supers = %{ pop || {} };
  0            
203            
204             # determine what package we are making a constructor for
205 0           my $package = caller;
206 0 0         if ( $package eq 'Class::Structured' ) {
207             # if our caller is just 'default_constructor', find our true caller
208 0           ($package) = caller(1);
209             }
210            
211             # mark ourself as the default constructor
212 0           my $varname = $package.'::!structured!.default_constructor';
213 0 0         ${ $varname } = $name unless defined ${ $varname };
  0            
  0            
214            
215             # iterate through parent classes, using either the specified
216             # constructor or the default constructor
217 0           my $parent;
218 0           my @parents = @{ $package.'::ISA' };
  0            
219 0           foreach $parent ( @parents ) {
220             # use the specified constructor, if there is one
221 0 0         next if exists $supers{$parent};
222            
223 0           my $default = ${ $parent.'::!structured!.default_constructor' };
  0            
224 0 0         $supers{$parent} = $default if defined $default;
225             }
226            
227             # now, define the constructor function
228 0           *{ $package.'::'.$name } =
229             sub {
230 0     0     my $type = shift;
231 0           my $self;
232            
233             # figure out how we were called
234 0 0         if ( ref($type) ) {
235 0           my $reftype = ref($type);
236 0 0         if ( $reftype eq $package ) {
    0          
237             # called with an instance of our own type
238 0           croak "Cloning is not yet supported by Class::Structured constructors - sorry!";
239             } elsif ( $reftype->isa( $package ) ) {
240             # called from below in the hierarchy
241 0           $self = $type;
242             }
243             } else {
244             # called as a constructor
245 0           $self = construct( $type );
246             }
247            
248             # call our parent constructors
249 0           my $parent;
250 0           foreach $parent ( keys %supers ) {
251 0           &{ $parent.'::'.$supers{$parent} }( $self, @_ );
  0            
252             }
253            
254             # call our own constructor
255 0 0         $code->( $self, @_ ) if $code;
256            
257 0           $self;
258 0           };
259             }
260            
261             =item default_constructor
262            
263             Creates a new constructor, and also marks it as the default.
264            
265             =cut
266             sub default_constructor {
267 0     0 1   my $package = caller;
268 0           ${ $package.'::!structured!.default_constructor' } = $_[0];
  0            
269 0           constructor( @_ );
270             }
271            
272             =item implementation
273            
274             Prototyped sub used to generate syntax
275            
276             =cut
277             sub implementation (&) {
278 0     0 1   $_[0];
279             }
280            
281             =item construct
282            
283             Internal function used to set up a class variable.
284            
285             =cut
286             sub construct {
287 0     0 1   my $package = shift;
288            
289             # check the abstracts
290 0 0         croak "Class $package has the following undefined abstracts and therefore cannot be created: ".
291             join ", ", list_abstracts( $package ) unless check_abstracts( $package );
292            
293             # add the public function, if necessary
294 0 0         unless ( defined *{ $package.'::public' }{CODE} ) {
  0            
295 0           *{ $package.'::public' } =
296             sub : lvalue {
297 0     0     $_[0]->{public}->{$_[1]};
298 0           };
299             }
300            
301             # bless the reference
302 0           bless {}, $package;
303             }
304            
305             # ------------------------------------------------------------------------
306             # Private and Public Variable Functions
307             # ------------------------------------------------------------------------
308            
309             =item define_variables
310            
311             =cut
312             sub define_variables {
313 0     0 1   my %params = @_;
314            
315             # determine what package we are in
316 0           my $package = caller;
317            
318             # iterate over the variables, defining each
319 0           my $var;
320 0           foreach $var ( keys %params ) {
321             # make sure the request is for a private variable
322 0 0         unless ( lc($params{$var}) eq 'private' ) {
323 0           carp "$var defined as unsupported type $params{$var}";
324 0           next;
325             }
326            
327             # add to the private variable list
328 0           my $list_name = $package.'::!structured!.privates';
329            
330 0 0         ${ $list_name } = Set::Scalar->new() unless defined ${ $list_name };
  0            
  0            
331 0           ${ $list_name }->insert( $var );
  0            
332            
333             # define the access function
334 0           *{ $package.'::'.$var } =
335             sub : lvalue {
336             # get our self
337 0     0     my $self = shift;
338            
339             # determine who called us
340 0           my $caller;
341 0           my $i = 0;
342 0           do {
343 0           ($caller) = caller($i++);
344             } while ($caller eq 'Class::Structured');
345            
346 0           my $list_name = $caller.'::!structured!.privates';
347 0 0 0       unless ( ($caller eq $package) ||
      0        
      0        
348             ( $package->isa( $caller ) && defined($$list_name) && $$list_name->member($var) )) {
349             # if the caller is not us our a superclass of us making a legitimate inquiry, die
350 0           croak "Invalid attempt to access variable $var in class $package from $caller";
351             }
352            
353 0           $self->{$caller}->{$var};
354 0           };
355             }
356            
357             }
358            
359             1;
360            
361             __END__