The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
By

Vincenzo Zocca

Creation

Sun Feb 16 20:29:26 CEST 2003

Update

Thu Sep 4 06:42:32 CEST 2003

0 USAGE NOTE

Although PerlBean produces decent code (decent enough for the author that is), PerlBean is still under development.

The code generation has impoved significantly since the first versions of PerlBean -which have been removed from CPAN- but still I expect more impovements. See the TODO list.

1 INTRODUCTION

Most OO designs contain objects with a bean like(*) structure and -optionally- some kind of logic. Although the bean capabilities in an object are -or should be- trivial, programming them takes a non proportional amount of time. Also, documenting inheritance of attributes and methods is tedious at best.

The modules in the PerlBean hierarchy are to make the life of a Perl OO programmer easier. They allow to setup classes with attributes and logic methods. These classes can be put into a bean collection and the Perl code for the classes is generated. The basic class documentation and the documentation of the attribute and method inheritance is generated.

At the time of writing (Thu Sep 4 06:42:32 CEST 2003), this package has already proved itself very useful for my professional purposes. I can code more in less time, I produce higher quality code, I find it easier to keep a conceptual overview of my projects and I take better advantage of OO paradigm altogether.

For the code to mature I 1) use it to generate itself and 2) use it to develop other projects and feed my findings back into the code. Currently I am at the point where the TODO list grows bigger but the items in it get more and more details.

(*)

The term "bean" is borrowed from Java. In this context it is important to know that beans are objects with attributes (or properties) which are accessible through methods like "get", "set" etc...

2 A PERL BEAN COLLECTION

PerlBean represents a Perl module and contains information on the package. Its descriptions, attributes, methods etc...

A PerlBean Collection is a collection of PerlBeans.

For this tutorial we'll develop a simple class hierarchy for geometrical shapes. The PerlBean Collection will consist of the abstract Shape, the implementations of classes Square and Circle and the Rectangle subclass of Square. This class hierarchy isn't exactly rocket science -it isn't even necessarily logical- but it serves the tutorial purpose.

2.1 Class diagram for shapes

                +------------+
                |   Shape    |
       + - - - -+ (abstract) +- - - - +
       |        |------------|        |
                |------------|        
       |        |   area()   |        |
      / \       +------------+       / \
     +-+-+                          +---+
       |                              |
                                       
       |                              |
 +-----+------+               +-------+----+
 |   Square   |               |   Circle   |
 |------------|               |------------|
 |   width    |               |   radius   |
 |------------|               |------------|
 |   area()   |               |   area()   |
 +-----+------+               +------------+
       |
      / \
     +---+
       |
 +-----+------+
 | Rectangle  |
 |------------|
 |   width    |
 |   height   |
 |------------|
 |   area()   |
 +------------+

2.2 Programming of the classes using PerlBean

The example.1.pl program generates code for the Shape packages.

2.2.1 PerlBean collection

First, a PerlBean::Collection is created which is used throughout the program. A collection-wide license is specified at construction.

 use PerlBean::Collection;
 my $coll = PerlBean::Collection->new( {
     license => <<EOF,
 This code is licensed under B<GNU GENERAL PUBLIC LICENSE>.
 Details on L<http://gnu.org>.
 EOF
 } );

2.2.2 PerlBean attribute factory

Then, a PerlBean::Attribute::Factory is created which is used to create attributes (PerlBean::Attribute(*) objects) throughout the program.

(*)

PerlBean::Attribute objects are an implementation of PerlBean::Method::Factory. That is, PerlBean::Attribute objects on their turn create PerlBean::Method objects.

 use PerlBean::Attribute::Factory;
 my $fact = PerlBean::Attribute::Factory->new();

2.2.3 Shape PerlBean

The first Shape PerlBean is created and added to the collection. At creation the package name, a short description and the abstract are specified. Read the PerlBean documentation for the details.

 use PerlBean;
 my $shape = PerlBean->new ( {
     package => 'Shape',
     short_description => 'geometrical shape package',
     abstract => 'geometrical shape package',
     autoloaded => 0,
 } );
 $coll->add_perl_bean( $shape );

2.2.4 area() interface method

The interface method area() is created and added to the Shape PerlBean. At creation the method name, the method description and the fact that the method is an interface is specified. Again, read the documentation for the details.

 use PerlBean::Method;
 my $meth = PerlBean::Method->new( {
     method_name => 'area',
     interface => 1,
     description => <<EOF,
 Calculates the area of the C<Shape> object.
 EOF
 } );
 $shape->add_method( $meth );

2.2.5 Circle PerlBean

Now the Circle PerlBean is created and added to the collection. The creation and addition to the collection is almost identical to Shape. The base attribute is specified to tell that Circle is a subclass -or implementation- of Shape.

 my $circle = PerlBean->new ( {
     package => 'Circle',
     base => [ qw( Shape ) ],
     short_description => 'circle shape',
     description => "circle shape\n",
     abstract => 'circle shape',
     autoloaded => 0,
 } );
 $coll->add_perl_bean( $circle );

2.2.6 Circle radius attribute

A radius attribute is created and added to the Circle PerlBean. At creation the attribute name, a short description and a regular expression to which the attribute's values must match (^\d*(\.\d+)?$) are specified. The documentation of PerlBean::Attribute::Factory has all the details for this part of the process.

 my $radius = $fact->create_attribute( {
     method_factory_name => 'radius',
     short_description => 'the shape\'s radius',
     allow_rx => [ qw( ^\d*(\.\d+)?$ ) ],
 } );
 $circle->add_method_factory( $radius );

2.2.7 Circle area() method

The implementation of method area() is created and added to the Circle PerlBean. At creation the method name and its body are specified. The description is copied from the interface when the code is generated.

 # Make the Circle area() method add it to the Circle PerlBean
 use PerlBean::Method;
 my $area_circle = PerlBean::Method->new( {
     method_name => 'area',
     body => <<EOF,
     my \$self = shift;
 
     return( 2 * 3.1415926 * \$self->get_radius() );
 EOF
 } );
 $circle->add_method( $area_circle );

2.2.8 Square with a width attribute and an area() method

This is much like 2.2.5 - 2.2.7 but then for Square.

 my $square = PerlBean->new ( {
     package => 'Square',
     base => [ qw( Shape ) ],
     short_description => 'square shape',
     abstract => 'square shape',
     autoloaded => 0,
 } );
 $coll->add_perl_bean( $square );

 my $width = $fact->create_attribute( {
     method_factory_name => 'width',
     short_description => 'the shape\'s width',
     allow_rx => [ qw( ^\d*(\.\d+)?$ ) ],
 } );
 $square->add_method_factory( $width );

 use PerlBean::Method;
 my $area_square = PerlBean::Method->new( {
     method_name => 'area',
     body => <<EOF,
     my \$self = shift;
 
     return( \$self->get_width() * \$self->get_width() );
 EOF
 } );
 $square->add_method( $area_square );

2.2.9 Rectangle with a height attribute and an area() method

Rectangle is a subclass from Square. It inherits the width attribute, has an additional height attribute and a different implementation of area().

The Rectangle PerlBean which is a subclass of Square.

 my $rectangle = PerlBean->new ( {
     package => 'Rectangle',
     base => [ qw( Square ) ],
     short_description => 'rectangle shape',
     abstract => 'rectangle shape',
     autoloaded => 0,
 } );
 $coll->add_perl_bean( $rectangle );

The height attribute.

 my $height = $fact->create_attribute( {
     method_factory_name => 'height',
     short_description => 'the shape\'s height',
     allow_rx => [ qw( ^\d*(\.\d+)?$ ) ],
 } );
 $rectangle->add_method_factory( $height );

The area() method

 use PerlBean::Method;
 my $area_rectangle = PerlBean::Method->new( {
     method_name => 'area',
     body => <<EOF,
     my \$self = shift;
 
     return( \$self->get_width() * \$self->get_height() );
 EOF
 } );
 $rectangle->add_method( $area_rectangle );

2.2.10 Code generation

The code is generated in directory example.1

 # The directory name
 my $dir = 'example.1';

 # Remove the old directory
 system ("rm -rf $dir");

 # Make the directory
 mkdir($dir);

 # Write the collection
 $coll->write($dir);

2.2.11 Documentation check

Check out the documentation of the PerlBeans. Try replacing Shape with Circle, Rectangle or Square.

 pod2man example.1/Shape.pm | nroff -man|less

 pod2man example.1/Shape.pm | nroff -man|less -r

 pod2html example.1/Shape.pm > example.1/Shape.html

2.2.12 Test the generated code

Program example.1.test.pl tests the classes Circle, Rectangle and Square.

 use strict;
 use lib qw( example.1 );
 
 use Circle;
 my $circle = Circle->new( {
     radius => 1,
 } );
 print 'Circle with radius ', $circle->get_radius(),
       ' has an area of ', $circle->area(), "\n";
 
 use Square;
 my $square = Square->new( {
     width => 1,
 } );
 print 'Square with width ', $square->get_width(),
       ' has an area of ', $square->area(), "\n";
 
 use Rectangle;
 my $rectangle = Rectangle->new( {
     width => 1,
     height => 2,
 } );
 print 'Rectangle with width ', $rectangle->get_width(),
       ' and with height ', $rectangle->get_height(),
       ' has an area of ', $rectangle->area(), "\n";

3 ATTRIBUTES

So far in the example from chapter 2 plain attributes were used (radius, width and height). The only advanced feature used is the allow_rx attribute when creating the attributes with the attribute factory.

But there is more. More attribute types and more features. The attribute factory knows how to select the correct attribute class and how to instantiate it.

3.1 PerlBean::Attribute::Single

So far we used the SINGLE attribute type which has set... and get... methods. The implementing class is called PerlBean::Attribute::Single.

3.2 PerlBean::Attribute::Boolean

The BOOLEAN attribute type has methods set... and is... and treats the value slightly different. The implementing class is called PerlBean::Attribute::Boolean.

3.3 PerlBean::Attribute::Multi

The attribute type MULTI is intended to contain array and hash derivatives and there are several classes that implement those. The implementing interface class is called PerlBean::Attribute::Multi.

3.3.1 PerlBean::Attribute::Multi::Ordered

The PerlBean::Attribute::Multi::Ordered class is obtained by specifying the attributes type => 'MULTI and ordered => 1 to the attribute factory. It implements a plain ordered list that can hold values in an ordered fashion.

3.3.2 PerlBean::Attribute::Multi::Unique

The PerlBean::Attribute::Multi::Unique class is obtained by specifying the attributes type => 'MULTI and unique => 1 to the factory. It implements a plain hash that can unique values (that is, any value is allowed to occur only once) in an unordered fashion.

3.3.3 PerlBean::Attribute::Multi::Unique::Ordered

The PerlBean::Attribute::Multi::Unique::Ordered is a hybrid of PerlBean::Attribute::Multi::Ordered and PerlBean::Attribute::Multi::Unique and is obtained by specifying the attributes type => 'MULTI, ordered => 1 and unique => 1. It implements an ordered list that can hold unique values.

3.3.4 PerlBean::Attribute::Multi::Unique::Associative

The PerlBean::Attribute::Multi::Unique::Associative class is obtained by specifying the attributes type => 'MULTI, unique => 1 and associative => 1 to the factory. It is a refinement of PerlBean::Attribute::Multi::Unique and associates unique keys to values. Well, this is actually even more a plain hash than PerlBean::Attribute::Multi::Unique.

3.3.5 PerlBean::Attribute::Multi::Unique::Associative::MethodKey

The PerlBean::Attribute::Multi::Unique::Associative::MethodKey class is obtained by specifying the attributes type => 'MULTI, unique => 1, associative => 1 and method_key => <method-key>to the factory. It is similar to PerlBean::Attribute::Multi::Unique::Associative but it takes the keys from the values by calling a method on the values. Very handy if you want to list objects by their keys.

3.3.6 Example containing all attribute types

Program example.2.pl contains a PerlBean collection with one PerlBean in it with all kinds of attributes. It doesn't do anything particularly useful other that serving the example purpose. Check out the program, the generated documentation and the generated code.

4 CONSTRUCTORS

The examples in the previous sections had PerlBean::Method objects. Well, there is also the PerlBean::Method::Constructor class that is an empty subclass of PerlBean::Method. Methods defined as PerlBean::Method::Constructor have the same properties as PerlBean::Method objects. They can be added to a PerlBean using the add_method() method. The only difference with PerlBean::Method objects is that the documentation is written in the METHODS section.

5 A FEW WORDS ON INHERITANCE

PerlBean takes inheritance into account when generating documentation and code. As Perl supports multiple inheritance, so does PerlBean.

Descriptions of methods and attributes are passed from superclass to subclass in a -hopefully- intuitive manner.

The string __SUPER_POD__ in descriptions is replaced with the description of the superclass. Most of the time you will not need this feature -which actually may need a few extra words in the documentation.

For interface methods a default method is implemented in the class where they are defined that merely throws an exception. This is to signal bad usage of the generated module.

6 STYLE

The default style is as defined in perlstyle. It can however be changed at will.

The style is controlled though the PerlBean::Style modules. The singleton instance of PerlBean::Style is used to format the generated code.

Example 3 calls the code from example 1 but changes the Style:

  • Method names in camel case

  • Block opening brace on new line

  • Space between function and brace

  • Tab indentation

The code to make the style change looks like:

 use strict;
 use PerlBean::Style;
 
 my $style = PerlBean::Style->instance();
 
 $style->set_method_factory_name_to_method_base_filter(\&mbase_flt);
 $style->set_method_operation_filter(\&op_ftl);
 $style->set_str_pre_block_open_curl("\n__IND_BLOCK__");
 $style->set_str_between_function_and_parenthesis(' ');
 $style->set_indent("\t");
 
 require 'example.1.pl';
 
 sub mbase_flt {
     my $ret = '';
     foreach my $attr_part ( split(/_+/, shift) ) {
         $ret .= ucfirst($attr_part);
     }
     return($ret);
 }
 
 sub op_ftl {
     return( ucfirst(shift) );
 }

NOTE: The PerlBean::Method objects are NOT affected by PerlBean::Style.

The PerlBean::Style instance is obtained through the instance() method.

set_method_factory_name_to_method_base_filter() allows to set the subroutine that converts an attribute name to the method base.

set_method_operation_filter() allows to set the subroutine that formats the method operation.

set_str_pre_block_open_curl() allows to set the string printed before the opening curly of a multi-line BLOCK.

set_str_between_function_and_parenthesis() allows to set the string between function name and its opening parenthesis.

set_indent() allows to set the string used for ONE indentation.

Check out the code and see for yourself.

7 SYMBOLS

The PerlBean::Symbol class allows for symbols to be declared that are global to the package.

Exporting of the symbols is also handeled by PerlBean::Symbol.

The tag description for exporting is handeled by PerlBean::Described::ExportTag.

8 DEPENDENCIES

Code dependencies (e.g. import, require and use) can be specified through packages PerlBean::Dependency::Import, PerlBean::Dependency::Require and PerlBean::Dependency::Use.

The dependency clases are listed at the top of the package.

9 AUTOLOADING

By default PerlBean generates autoloaded code. You can switch the autoloaded attribute off in PerlBean objects to generated plain Perl code. See also the examples.

10 GET INVOLVED

Your comments are valuable!

As I am working at this project on my own there's the usual chance my vision is limited in several areas. That may show in certain bits of -generated- code. Particularly in this area, I'm interested in your feedback.

Bug reports are welcome too.

Vincenzo Zocca mailto:Vincenzo@Zocca.DO.NOT.SPAM.com