########################################################################
# Plugin::Installer
#
# fodder for use base.
#
# Implements AUTOLOAD, DESTROY. 
########################################################################

package Plugin::Installer;

use strict;

use Carp;
use Symbol;
use Scalar::Util qw( &reftype );

# default is to install and dispatch (iff possible) 
# the compiler results; not storing special metadata
# for any methods.

my $meta = {};

my $default_meta = 
{

    install     => 1,
    dispatch    => 1,
    storemeta   => 0,

    alt_package => '',

};

our $AUTOLOAD = '';

AUTOLOAD
{
    use Symbol;
    use Scalar::Util;

    my $obj  = $_[0];

    my ( $class, $name ) = $AUTOLOAD =~ m{^ (.+) :: (\w+) $ }x;

    # Note: metadata is cached for the CLASS not 
    # the individual objects. if someone wants 
    # object-level metadata they can deal with 
    # it in their compiler.
    #
    # languages can udpate the metadata to 
    # tickle the switches for the class (with 
    # install set) or a single method (with 
    # install false).

    my %method_meta
    = %{ $meta->{ $class } ||= $default_meta };

    local $obj->{ install_meta } = \%method_meta;

    # note that this will install any referent 
    # it is handed back: code or data. this is a 
    # quick way to hand back data (via install + 
    # nodispatch).
    #
    # reftype can't handle unblessed ref's, so the
    # fix is to eval it and use ref as a fallback. 

    if( defined ( my $result = eval{ $obj->compile($name) } ) )
    {
        if( $method_meta{store_meta} )
        {
            $meta->{ $class } = \%method_meta;
        }

        # no way to install or dispatch a non-referent...

        if( my $type = eval { reftype $result } || ref $result )
        {
            if( $method_meta{ install } )
            {
                # check if they have a better idea where to 
                # install the thing than the caller.

                # the caller gets SOMEthing installed in any
                # case: even if it's a scalar.

                my $package
                = $method_meta{ alt_package } || $class;

                my $ref = qualify_to_ref $name, $package;

                # note: undef of coderefs avoids warnings.

                undef &{$ref} if $type eq 'CODE';

                *$ref = $result;
            }

            if( $type eq 'CODE' && $method_meta{ dispatch } )
            {
                goto &$result
            }
        }
        elsif( $result )
        {
            # false-but-defined is good enough to avoid
            # installation, anything true is proabably
            # a bogus call.

            warn "Oddity: true non-referent result from '$name':\n$result\n";
        }
    }
    elsif( $@ )
    {
        croak "Compile aborted for '$class' handling '$name'";
    }

}

DESTROY
{
    # stub destroy, saves hitting the AUTOLOAD
    # for every destructor. classes that need 
    # their own DESTROY can override this easily
    # enough...
    # 
    # the object doesn't have to bookkeep any of
    # the class-based metadata so there isn't any
    # metadata maintinence here.

    ()
}

# keep use happy

1

__END__

=head1 NAME

Plugin::Installer

Call the plugin's compiler, install it via quality_to_ref, then
dispatch it using goto.

=head1 SYNOPSIS 

    package Myplugin;

    use base qw( Plugin::Installer Plugin::Language::Foobar );

    ...

    my $plugin = Myplugin->construct;

    # frobnicate is passed first to Plugin::Installer 
    # via AUTOLOAD, then to P::L::Foobar's compile
    # method. if what comes back from the compiler is
    # a referent it is intalled in the P::L::F namespace
    # and if it is a code referent it is dispatched.

    $plugin->frobnicate;

=head1 DESCRIPTION

The Plugin framework gives runtime compile, 
install, and dispatch of user-defined code.
The code doesn't have to be Perl, just something
that the object handling it can compile. 

=head2 AUTOLOAD

Extracts the package and method name from a call,
dispatches $package->compile( $name ), and handles
the result. Results can be installed (if they are
referents of any time) and dispatched (if they are
coderefs).

=head2 DESTROY

Stub, saves passing back through the AUTOLOAD
unnecessarly. If a class needs to do something
for itslef they should implement a DESTROY.

=head2 Plugin install metadata

During compilation, Plugin::Install::AUTOLOAD
places an {install_meta} entry into the object.
This defines switches used for post-compile 
handling:

    my $default_meta = 
    {
        install     => 1,
        dispatch    => 1,
        storemeta   => 0,

        alt_package => '',
    };

 
=over 4

=item install     => 1,

Does a referent returned from $obj->compile get installed
into the namespace or simply dispatched?

This is used to avoid installing plugins whose 
contents will be re-defined during execution
and called multiple times. 

=item dispatch    => 1,

Is a code referent dispatched (whether or not it is 
installed into a package)?

Some methods may be easier to pre-install but not 
dispatch immediately (e.g. if they involve expensive
startup but have per-execution side-effects). Setting
this to false will skip dispatch of coderef's even
if they are installed.

=item alt_package => '',

Package to install the referent into (default if
false is the object's package). This the namespace
passed with the method name to 'qualify_to_ref'.

This can be used by the compiler to install data
or coderef's into a caller's namespace (e.g. via
caller(2)). If this is used with storemeta then 
ALL of the methods for the plugin class will be 
installed into the alternate package space unless
they set their own alt_package when called.

=item storemeta   => 0,

Store the current metdata as the default for this
class? The metadata is stored by class name, allowing
an initial "startup" call (say in the constructor
or import) to configure appropriate defaults for the
entire class.

=back

Note that if install is true for a coderef then 
none of these matter much after the first call
since the installed method will bypass the 
AUTOLOAD.

Corrilary: If a "setup" method is used to set 
metadata values then it probably should not be 
installed so that it can fondle the class' 
metadata and modify it if necewsary on later 
calls.

This also means that plugin languages should
implement some sort of instructions to modify
the metadata.

=head1 SEE ALSO

=over 4

=item ./t/01.t

Example plugin class with simple, working
compiler.

=item Plugin::Language::DML

Little language for bulk data filtering,
including pre- and post-processing DBI calls.

=item Symbol

Installing symbols without resoting to no strict 'refs'.

=item Scalar::Util 

Extracting the basetype of a blessed referent.

=back

=head1 AUTHORS

Steven Lembark  <lembark@wrkhors.com>
Florian Mayr    <florian.mayr@gmail.com>

=head1 COPYRIGHT

Copyright (C) 2005 by the authors; this code can 
be reused and re-released under the same terms 
as Perl-5.8.0 or any later version of the Perl 
Artistic License.

