#!/usr/local/bin/perl
=head1 NAME
Class::Structured - provides a more structured class system for Perl
=head1 DESCRIPTION
Specifically, this function provides for variables with access specifiers
that will inherit properly, for constructors, and for abstract functions.
Abstract functions may be used on their own with no performance penalty.
Constructors and access specified variables each imply the use of the other -
and will incur a semi-significant performance penalty.
Also, note that when using all of the features it can cause problems to define
an AUTOLOAD function - so please don't.
=head1 HISTORY
=over 2
=item *
02/04/02 - Robby Walker - released - version 0.1
=item *
12/10/01 - Robby Walker - added private variable support, tested - version 0.003
=item *
12/06/01 - Robby Walker - adding abstract listing, checking and constructors - version 0.002
=item *
12/05/01 - Robby Walker - created the file, wrote abstract support - version 0.001
=back
=head1 METHODS
=over 4
=cut
#----------------------------------------------------------
package Class::Structured;
# MODULE METADATA
our $VERSION = 0.1;
our @ISA = qw(Exporter);
our @EXPORT = ();
our @EXPORT_OK = qw(declare_abstract implementation constructor default_constructor define_variables);
our %EXPORT_TAGS = (
all => [qw(declare_abstract implementation constructor default_constructor define_variables)]
);
# PRAGMATIC DEPENDENCIES
use strict "vars";
use strict "subs";
use warnings;
# OUTSIDE DEPENDENCIES
use Carp;
use Set::Scalar;
# ========================================================================
# METHODS
# ========================================================================
# ------------------------------------------------------------------------
# Methods for abstract functions
# ------------------------------------------------------------------------
=item declare_abstract
Declares an abstract function in the current package.
=cut
sub declare_abstract {
my $function_name = pop; # get last param as function name
my $package = caller;
# update the abstract list (keep it as a weird name so we don't have a collision with a real variable name)
my $list_name = $package.'::'.'!structured!.abstracts';
${ $list_name } = Set::Scalar->new() unless defined ${ $list_name };
${ $list_name }->insert( $function_name );
# declare the function
*{ $package.'::'.$function_name } =
sub {
croak "$function_name in class $package is declared abstract, and cannot be called";
};
}
=item list_abstracts
Provides a list of all the abstracts left by a package for subclasses to implement.
=cut
sub list_abstracts {
my $package = shift;
# create a set to list all abstracts
my $plist_name = $package.'::!structured!.abstracts';
my $list;
# add all locally declared abstracts - as definites
if ( defined ${ $plist_name } ) {
$list = ${ $plist_name }->clone;
} else {
$list = Set::Scalar->new;
}
# get a set for each parent class's abstracts
my %parents;
my $parent;
my @parents = @{ $package.'::ISA' };
foreach $parent ( @parents ) {
my @abstracts = list_abstracts($parent);
if ( @abstracts + 0 ) {
$parents{$parent} = Set::Scalar->new(@abstracts);
}
}
# this variable holds a list of functions we know to be implemented (i.e. not abstract)
my $notlist = Set::Scalar->new;
# now, step over each parent, adding abstracts when no other parent implements that function
# note that this code makes no allowance for AUTOLOAD, which is why we state earlier that this
# Perl feature should be avoided when using Class::Structured
foreach $parent (keys %parents) {
my $function;
my @abstracts = $parents{$parent}->members;
foreach $function (@abstracts) {
# skip this if we already know the function to be abstract or implemented
next if ($list->member($function) || $notlist->member($function));
my $can;
if ( defined *{ $package.'::'.$function }{CODE} ) {
# does this package override it?
$can = 1;
} else {
# does one of this package's parents override it
my $other;
$can = 0;
foreach $other (@parents) {
next if ($other eq $parent);
# if the parent can run the function, and not just because it
# declares it abstract, mark the function as implemented
if ( !((exists $parents{$other}) && ($parents{$other}->member($function)))
&& $other->can( $function ) )
{
$can = 1;
last;
}
}
}
# add to the appropriate list
($can ? $notlist : $list)->insert( $function );
}
}
my @members = $list->members;
return @members;
}
=item check_abstracts
When instantiating a class, make sure that it has declared all the necessary abstracts
=cut
sub check_abstracts {
my $package = shift;
# if we have no abstracts, we are OK
return ! ( list_abstracts($package) + 0 );
}
# ------------------------------------------------------------------------
# Constructor related functions
# ------------------------------------------------------------------------
=item constructor
Creates a new constructor.
=cut
sub constructor {
my $name = shift;
# load parameters, doing some aerobics to ensure their proper loading
my $code = pop || sub {};
my %supers = %{ pop || {} };
# determine what package we are making a constructor for
my $package = caller;
if ( $package eq 'Class::Structured' ) {
# if our caller is just 'default_constructor', find our true caller
($package) = caller(1);
}
# mark ourself as the default constructor
my $varname = $package.'::!structured!.default_constructor';
${ $varname } = $name unless defined ${ $varname };
# iterate through parent classes, using either the specified
# constructor or the default constructor
my $parent;
my @parents = @{ $package.'::ISA' };
foreach $parent ( @parents ) {
# use the specified constructor, if there is one
next if exists $supers{$parent};
my $default = ${ $parent.'::!structured!.default_constructor' };
$supers{$parent} = $default if defined $default;
}
# now, define the constructor function
*{ $package.'::'.$name } =
sub {
my $type = shift;
my $self;
# figure out how we were called
if ( ref($type) ) {
my $reftype = ref($type);
if ( $reftype eq $package ) {
# called with an instance of our own type
croak "Cloning is not yet supported by Class::Structured constructors - sorry!";
} elsif ( $reftype->isa( $package ) ) {
# called from below in the hierarchy
$self = $type;
}
} else {
# called as a constructor
$self = construct( $type );
}
# call our parent constructors
my $parent;
foreach $parent ( keys %supers ) {
&{ $parent.'::'.$supers{$parent} }( $self, @_ );
}
# call our own constructor
$code->( $self, @_ ) if $code;
$self;
};
}
=item default_constructor
Creates a new constructor, and also marks it as the default.
=cut
sub default_constructor {
my $package = caller;
${ $package.'::!structured!.default_constructor' } = $_[0];
constructor( @_ );
}
=item implementation
Prototyped sub used to generate syntax
=cut
sub implementation (&) {
$_[0];
}
=item construct
Internal function used to set up a class variable.
=cut
sub construct {
my $package = shift;
# check the abstracts
croak "Class $package has the following undefined abstracts and therefore cannot be created: ".
join ", ", list_abstracts( $package ) unless check_abstracts( $package );
# add the public function, if necessary
unless ( defined *{ $package.'::public' }{CODE} ) {
*{ $package.'::public' } =
sub : lvalue {
$_[0]->{public}->{$_[1]};
};
}
# bless the reference
bless {}, $package;
}
# ------------------------------------------------------------------------
# Private and Public Variable Functions
# ------------------------------------------------------------------------
=item define_variables
=cut
sub define_variables {
my %params = @_;
# determine what package we are in
my $package = caller;
# iterate over the variables, defining each
my $var;
foreach $var ( keys %params ) {
# make sure the request is for a private variable
unless ( lc($params{$var}) eq 'private' ) {
carp "$var defined as unsupported type $params{$var}";
next;
}
# add to the private variable list
my $list_name = $package.'::!structured!.privates';
${ $list_name } = Set::Scalar->new() unless defined ${ $list_name };
${ $list_name }->insert( $var );
# define the access function
*{ $package.'::'.$var } =
sub : lvalue {
# get our self
my $self = shift;
# determine who called us
my $caller;
my $i = 0;
do {
($caller) = caller($i++);
} while ($caller eq 'Class::Structured');
my $list_name = $caller.'::!structured!.privates';
unless ( ($caller eq $package) ||
( $package->isa( $caller ) && defined($$list_name) && $$list_name->member($var) )) {
# if the caller is not us our a superclass of us making a legitimate inquiry, die
croak "Invalid attempt to access variable $var in class $package from $caller";
}
$self->{$caller}->{$var};
};
}
}
1;
__END__
=pod
=back
=head1 TODO
=over 4
=item *
Allow for parent constructor parameter specification.
=back
=head1 BUGS
Probably some
=head1 AUTHORS AND COPYRIGHT
Written by Robby Walker for Yet Another Perl Journal,
CD-Lab (www.cd-lab.com), and Point Writer (www.pointwriter.com).
All Rights Reserved.
=cut