Projekt

Allgemein

Profil

Herunterladen (75,3 KB) Statistiken
| Zweig: | Markierung: | Revision:
=head1 NAME

Exception::Lite - light weight exception handling class with smart
stack tracing, chaining, and localization support.

=head1 SYNOPSIS

# --------------------------------------------------------
# making this module available to your code
# --------------------------------------------------------

#Note: there are NO automatic exports

use Exception::Lite qw(declareExceptionClass
isException
isChainable
onDie
onWarn);

# imports only: declareExceptionClass isException isChainable
use Exception::Lite qw(:common);

# imports all exportable methods listed above
use Exception::Lite qw(:all);


# --------------------------------------------------------
# declare an exception class
# --------------------------------------------------------

# no format rule
declareExceptionClass($sClass);
declareExceptionClass($sClass, $sSuperClass);

# with format rule
declareExceptionClass($sClass, $aFormatRule);
declareExceptionClass($sClass, $sSuperClass, $aFormatRule);

# with customized subclass
declareExceptionClass($sClass, $sSuperClass, 1);
declareExceptionClass($sClass, $aFormatRule, 1);
declareExceptionClass($sClass, $sSuperClass, $aFormatRule, 1);

# --------------------------------------------------------
# throw an exception
# --------------------------------------------------------

die $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule
die $sClass->new($prop1 => $val1, ...); #has format rule

#-or-

$e = $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule
$e = $sClass->new($prop1 => $val1, ...); #has format rule

die $e;

# --------------------------------------------------------
# catch and test an exception
# --------------------------------------------------------

# Note: for an explanation of why we don't use if ($@)... here,
# see Catching and Rethrowing exceptions below

eval {
.... some code that may die here ...
return 1;
} or do {
my $e=$@;

if (isException($e, 'Class1')) {
... do something ...
} elsif (isExcption($e, 'Class2')) {
... do something else ...
}
};

isException($e); # does $e have the above exception methods?
isException($e,$sClass) # does $e belong to $sClass or a subclass?

# --------------------------------------------------------
# getting information about an exception object
# --------------------------------------------------------

$e->getMessage();
$e->getProperty($sName);
$e->isProperty($sName);
$e->replaceProperties($hOverride);

$e->getPid();
$e->getPackage();
$e->getTid();

$e->getStackTrace();
$e->getFrameCount();
$e->getFile($i);
$e->getLine($i);
$e->getSubroutine($i);
$e->getArgs($i);

$e->getPropagation();
$e->getChained();


# --------------------------------------------------------
# rethrowing exceptions
# --------------------------------------------------------

# using original properties and message

$@=$e; die; # pure Perl way (reset $@ in case wiped out)

die $e->rethrow(); # same thing, but a little less cryptic


# overriding original message/properties

die $e->rethrow(path=>$altpath, user=>$nameReplacingId);


# --------------------------------------------------------
# creation of chained exceptions (one triggered by another)
# (new exception with "memory" of what caused it and stack
# trace from point of cause to point of capture)
# --------------------------------------------------------

isChainable($e); # can $e be used as a chained exception?

die $sClass->new($e, $sMsg, $prop1 => $val1, ...);#no format rule
die $sClass->new($e, $prop1 => $val1, ...); #has format rule

# --------------------------------------------------------
# print out full message from an exception
# --------------------------------------------------------

print $e # print works
warn $e # warn works
print "$e\n"; # double quotes work
my $sMsg=$e."\n"; print $sMsg; # . operator works


# --------------------------------------------------------
# global control variables (maybe set on the command line)
# --------------------------------------------------------

$Exception::Lite::STRINGIFY #set rule for stringifying messages

= 1; # message and file/line where it occured
= 2; # 1 + what called what (simplified stack trace)
= 3; # 2 + plus any chained exceptions and where message
# was caught, if propagated and rethrown
= 4; # 3 + arguments given to each call in stack trace
= coderef # custom formatting routine

$Exception::Lite::TAB # set indentation for stringified
# messages, particularly indentation for
# call parameters and chained exceptions

$Exception::Lite::FILTER
= 0 # see stack exactly as Perl does
= 1 # remove frames added by eval blocks
= coderef # custom filter - see getStackTrace for details

# --------------------------------------------------------
# controlling the stack trace from the command line
# --------------------------------------------------------

perl -mException::Lite=STRINGIFY=1,FILTER=0,TAB=4
perl -m'Exception::Lite qw(STRINGIFY=1 FILTER=0 TAB=4)'

# --------------------------------------------------------
# built in exception classes
# --------------------------------------------------------

# generic wrapper for converting exception strings and other
# non-Exception::Lite exceptions into exception objects

Exception::Class::Any->new($sMessageText);

To assist in debugging and testing, this package also includes
two methods that set handlers for die and warn. These methods
should I<only> be used temporarily during active debugging. They
should not be used in production software, least they interfere
with the way other programmers using your module wish to do their
debugging and testing.

# --------------------------------------------------------
# force all exceptions/warnings to use Exception::Lite to
# print out messages and stack traces
# --------------------------------------------------------

# $stringify is the value for EXCEPTION::Lite::STRINGIFY
# that you want to use locally to print out messages. It
# will have no effect outside of the die handler

Exception::Lite::onDie($stringify);
Exception::Lite::onWarn($stringify);

=head1 DESCRIPTION

The C<Exception::Lite> class provides an easy and very light weight
way to generate context aware exceptions. It was developed because
the exception modules on CPAN as of December,2010 were heavy on
features I didn't care for and did not have the features I most
needed to test and debug code efficiently.

=head2 Features

This module provides a light weight but powerful exception class
that

=over

=item *

provides an uncluttered stack trace that clearly shows what
called what and what exception triggered what other exception.
It significantly improves on the readability of the stack trace
dumps provided by C<carp> and other exception modules on
CPAN (as of 12/2010). For further discussion and a sample, see
L</More intelligent stack trace>.

=item *

gives the user full control over the amount of debugging
information displayed when exceptions are thrown.

=item *

permits global changes to the amount of debugging information
displayed via the command line.

=item *

closely integrates exception classes, messages, and properties
so that they never get out of sync with one another. This in
turn eliminates redundant coding and helps reduce the cost of
writing,validating and maintaining a set of exceptions.

=item *

is easy to retrofit with native language support, even if this
need appears late in the development process.This makes it
suitable for use with agile development strategies.

=item *

act like strings in string context but are in fact objects with
a class hierarchy and properties.They can be thrown and rethrown
with standard Perl syntax. Like any object, they can be uniquely
identified in numeric context where they equal their reference
address (the value returned by C<Scalar::Util::refaddr()>.

=item *

does not interfere with signal handlers or the normal Perl syntax
and the assumptions of Perl operators.

=item *

can be easily extended and subclassed

=back

=head2 Lightweight how?

Despite these features C<Exception::Lite> maintains its "lite"
status by

=over

=item *

using only core modules

=item *

generating tiny exception classes (30-45LOC per class).

=item *

eliminating excess baggage by customizing generated classes to
reflect the actual needs of exception message generation. For
instance an exception wrapped around a fixed string message would
omit code for message/property integration and would be little
more than a string tied to a stack trace and property hash.

=item *

storing only the minimum amount of stack trace data needed to
generate exception messages and avoiding holding onto references
from dead stack frames. (Note: some CPAN modules hold onto
actual variables from each frame, possibly interfering with
garbage collection).

=item *

doing all its work, including class generation and utilities in
a single file that is less than half the size of the next smallest
similarly featured all-core exception class on CPAN (support for
both properties and a class heirarchy). C<Exception::Lite>
contains about 400 lines when developer comments are excluded). The
next smallest all core module is L<Exception::Base|Exception::Base>
which clocks in at just over 1000 lines after pod and developer
comments are excluded).

=item *

avoiding a heavy-weight base class. Code shared by
C<Exception::Lite> classes are stored in function calls that total
230 or so lines of code relying on nothing but core modules. This
is significantly less code than is needed by the two CPAN packages
with comparable features. The all core
L<Exception::Base|Exception::Base> class contains 700+ lines of
code. The base class of L<Exception::Class|Exception::Class> has
200 lines of its own but drags in two rather large non-core
modules as dependencies: L<Devel::StackTrace|Devel::StackTrace>
L<Class::Data::Inheritable|Class::Data::Inheritable>.

=back

C<Exception::Lite> has more features (chaining, message/property
integration) but less code due to the following factors:

=over

=item *

working with Perl syntax rather than trying to replace it.

=item *

using a light approach to OOP - exception classes have just enough
and no more OO features than are needed to be categorized by a
class, participate in a class heirarchy and to have properties.

=item *

respecting separation of concerns. C<Exception::Lite> focuses
on the core responsibility of an exception and leaves the bulk of
syntax creation (e.g. Try/Catch) to specialist modules like
L<Try::Tiny|Try::Tiny>. Other modules try to double as
comprehensive providers of exception related syntactic sugar.

=item *

not trying to be the only kind of exception that an application
uses.

=back

=head1 USAGE

=head2 Defining Exception Classes

C<Exception::Lite> provides two different ways to define messages.
The first way, without a format rule, lets you compose a freeform
message for each exception. The second way, with a format rule,
lets you closely integrate messages and properties and facilitates
localization of messages for any packages using your software.

=head3 Defining freeform messages

If you want to compose a free form message for each and every
exception, the class declaration is very simple:

declareExceptionClass($sClass);
declareExceptionClass($sClass, $sSuperClass);

# with customized subclass
declareExceptionClass($sClass, $sSuperClass, 1);

C<$sClass> is the name of the exception class.

C<$sSuperClass> is the name of the superclass, if there is one.
The superclass can be any class created by C<Exception::Lite>. It
can also be any role class, i.e. a class that has methods but no
object data of its own.

The downside of this simple exception class is that there is
absolutely no integration of your messages and any properties that
you assign to the exception. If you would like to see your property
values included in the message string,consider using a formatted
message instead.

=head3 Defining formatted messages

If you wish to include property values in your messages, you need
to declare a formatted message class. To do this, you define a
format rule and pass it to the constructor:

$aFormatRule = ['Cannot copy %s to %s', qw(from to) ];

declareExceptionClass($sClass, $aFormatRule);
declareExceptionClass($sClass, $sSuperClass, $aFormatRule);

# with customized subclass
declareExceptionClass($sClass, $aFormatRule, 1);
declareExceptionClass($sClass, $sSuperClass, $aFormatRule, 1);

Format rules are nothing more than a sprintf message string
followed by a list of properties in the same order as the
placeholders in the message string. Later on when an exception
is generated, the values of the properties will replace the
property names. Some more examples of format rules:


$aFormatRule = ['Illegal argument <%s>: %s', qw(arg reason)];
declareExceptionClass('BadArg', $aFormatRule);

$aFormatRule = ['Cannot open file <%s>> %s', qw(file reason)];
declareExceptionClass('OpenFailed', $aFormatRule);

$sFormatRule = ['Too few %s, must be at least %s', qw(item min)];
declareExceptionClass('TooFewWidgets', $aFormatRule);


Later on when you throw an exception you can forget about the message
and set the properties, the class will do the rest of the work:

die BadArg->new(arg=>$sPassword, reason=>'Too few characters');


open(my $fh, '>', $sFile)
or die OpenFailed->new(file=>$sFile, reason=>$!);

And still later when you catch the exception, you have two kinds
of information for the price of one:

# if you catch BadArg

$e->getProperty('arg') # mine
$e->getProperty('reason') # too few characters
$e->getMessage() # Illegal argument <mine>: too few characters


# if you catch OpenFailed

$e->getProperty('file') # foo.txt
$e->getProperty('reason') # path not found
$e->getMessage() # Cannot open <foo.txt>: path not found


=head2 Creating and throwing exceptions

When it comes times to create an exception, you create and
throw it like this (C<$sClass> is a placeholder for the name of
your exception class);


die $sClass->new($sMsg, prop1 => $val1, ...); #no format rule
die $sClass->new(prop1 => $val1, ...); #has format rule

#-or-

$e = $sClass->new($sMsg, prop1 => $val1, ...); #no format rule
$e = $sClass->new(prop1 => $val1, ...); #has format rule

die $e;


For example:

# Freeform exceptions (caller composes message, has message
# parameter ($sMsg) before the list of properties)

close $fh or die UnexpectedException
->new("Couldn't close file handle (huh?): $!");

die PropertySettingError("Couldn't set property"
, prop=>foo, value=>bar);

# Formatted exceptions (no $sMsg parameter)

if (length($sPassword) < 8) {
die BadArg->new(arg=>$sPassword, reason=>'Too few characters');
}

open(my $fh, '>', $sFile)
or die OpenFailed->new(file=>$sFile, reason=>$!);

In the above examples the order of the properties does not matter.
C<Exception::Lite> is using the property names, not the order of
the properties to find the right value to plug into the message
format string.

=head2 Catching and testing exceptions

In Perl there are two basic ways to work with exceptions:

* native Perl syntax

* Java like syntax (requires non-core modules)

=head3 Catching exceptions the Java way

Java uses the following idiom to catch exceptions:

try {
.... some code here ...
} catch (SomeExceptionClass e) {
... error handling code here ...
} catch (SomeOtherExceptionClass e) {
... error handling code here ...
} finally {
... cleanup code here ...
}

There are several CPAN modules that provide some sort of syntactic
sugar so that you can emulate java syntax. The one recommended
for C<Exception::Lite> users is L<Try::Tiny|Try::Tiny>.
L<Try::Tiny|Try::Tiny> is an elegant class that concerns itself
only with making it possible to use java-like syntax. It can be
used with any sort of exception.

Some of the other CPAN modules that provide java syntax also
require that you use their exception classes because the java like
syntax is part of the class definition rather than a pure
manipulation of Perl syntax.


=head3 Catching exceptions the Perl way

The most reliable and fastest way to catch an exception is to use
C< eval/do >:

eval {
...
return 1;
} or do {
# save $@ before using it - it can easily be clobbered
my $e=$@;

... do something with the exception ...

warn $e; #use $e as a string
warn $e->getMessage(); # use $e as an object
}


The C<eval> block ends with C<return 1;> to insure that successful
completion of the eval block never results in an undefined value.
In certain cases C<undef> is a valid return value for a statement,
We don't want to enter the C<do> block for any reason other than
a thrown exception.

C< eval/do > is both faster and more reliable than the C< eval/if>
which is commonly promoted in Perl programming tutorials:

# eval ... if

eval {...};
if ($@) {....}

It is faster because the C<do> block is executed if and only
if the eval fails. By contrast the C<if> must be evaluated both
in cases of succes and failure.

C< eval/do > is more reliable because the C<do> block is guaranteed
to be triggered by any die, even one that accidentally throws undef
or '' as the "exception". If an exception is thrown within the C<eval>
block, it will always evaluate to C<undef> therefore triggering the
C<do> block.

On the other hand we can't guarentee that C<$@> will be defined
even if an exception is thrown. If C<$@> is C<0>, C<undef>, or an
empty string, the C<if> block will never be entered. This happens
more often then many programmers realize. When eval exits the
C< eval > block, it calls destructors of any C<my> variables. If
any of those has an C< eval > statement, then the value of C<$@> is
wiped clean or reset to the exception generated by the destructor.

Within the C<do> block, it is a good idea to save C<$@> immediately
into a variable before doing any additional work. Any subroutine
you call might also clobber it. Even built-in commands that don't
normally set C<$@> can because Perl lets a programmer override
built-ins with user defined routines and those user define routines
might set C<$@> even if the built-in does not.

=head3 Testing exceptions

Often when we catch an exception we want to ignore some, rethrow
others, and in still other cases, fix the problem. Thus we need a
way to tell what kind of exception we've caught. C<Exception::Lite>
provides the C<isException> method for this purpose. It can be
passed any exception, including scalar exceptions:

# true if this exception was generated by Exception::Line
isException($e);


# true if this exception belongs to $sClass. It may be a member
# of the class or a subclass. C<$sClass> may be any class, not
# just an Exception::Lite generated class. You can even use this
# method to test for string (scalar) exceptions:

isException($e,$sClass);

isException($e,'Excption::Class');
isException($e, 'BadArg');
isException($e, '');

And here is an example in action. It converts an exception to a
warning and determines how to do it by checing the class.


eval {
...
return 1;
} or do {
my $e=$@;
if (Exception::Lite::isException($e)) {

# get message w/o stack trace, "$e" would produce trace
warn $e->getMessage();

} elsif (Exception::Lite::isException('Exception::Class') {

# get message w/o stack trace, "$e" would produce trace
warn $e->message();

} elsif (Exception::Lite::isException($e,'')) {

warn $e;
}
}

=head2 Rethrowing exceptions

Perl doesn't have a C<rethrow> statement. To reliably rethrow an
exception, you must set C<$@> to the original exception (in case it
has been clobbered during the error handling process) and then call
C<die> without any arguments.

eval {
...
return 1;
} or do {
my $e=$@;

# do some stuff

# rethrow $e
$@=$e; die;
}

The above code will cause the exception's C<PROPAGATE> method to
record the file and line number where the exception is rethrown.
See C<getLine>, C<getFile>, and C<getPropagation> in the class
reference below for more information.

As this Perl syntax is not exactly screaming "I'm a rethrow",
C<Exception::Lite> provides an alternative and hopefully more
intuitive way of propagating an exception. There is no magic here,
it just does what perl would do had you used the normal syntax,
i.e. call the exception's C<PROPAGATE> method.

eval {
...
return 1;
} or do {
my $e=$@;

# rethrow $e
die $e->rethrow();
}

=head2 Chaining Messages

As an exception moves up the stack, its meaning may change. For
example, suppose a subroutine throws the message "File not open".
The immediate caller might be able to use that to try and open
a different file. On the other hand, if the message gets thrown
up the stack, the fact that a file failed to open might not
have any meaning at all. That higher level code only cares that
the data it needed wasn't available. When it notifies the user,
it isn't going to say "File not found", but "Can't run market
report: missing data feed.".

When the meaning of the exception changes, it is normal to throw
a new exception with a class and message that captures the new
meaning. However, if this is all we do, we lose the original
source of the problem.

Enter chaining. Chaining is the process of making one exception
"know" what other exception caused it. You can create a new
exception without losing track of the original source of the
problem.

To chain exceptions is simple: just create a new exception and
pass the caught exception as the first parameter to C<new>. So
long as the exception is a non-scalar, it will be interpreted
as a chained exception and not a property name or message text
(the normal first parameter of C<new>).

Chaining is efficient, especially if the chained exception is
another C<Exception::Lite> exception. It does not replicate
the stack trace. Rather the original stack trace is shorted to
include only the those fromes frome the time it was created to
the time it was chained.

Any non-scalar exception can be chained. To test whether or not
a caught exception is chainable, you can use the method
C<isChainable>. This method is really nothing more than
a check to see if the exception is a non-scalar, but it helps
to make your code more self documenting if you use that method
rather than C<if (ref($e))>.

If an exception isn't chainable, and you still want to chain
it, you can wrap the exception in an exception class. You
can use the built-in C<Exception::Class::Any> or any class of
your own choosing.

#-----------------------------------------------------
# define some classes
#-----------------------------------------------------

# no format rule
declareExceptionClass('HouseholdDisaster');

# format rule
declareExceptionClass('ProjectDelay'
, ['The project was delayed % days', qw(days)]);

#-----------------------------------------------------
# chain some exceptins
#-----------------------------------------------------

eval {
.... some code here ...
return 1;
} or do {
my $e=$@;
if (Exception::Lite::isChainable($e)) {
if (Exception::Lite::isException($e, 'FooErr') {
die 'SomeNoFormatException'->new($e, "Caught a foo");
} else {
die 'SomeFormattedException'->new($e, when => 'today');
}
} elsif ($e =~ /fire/) {
die 'Exception::Lite::Any'->new($e);
die 'SomeFormattedException'->new($e, when => 'today');
} else {
# rethrow it since we can't chain it
$@=$e; die;
}
}

=head2 Reading Stack Traces

At its fullest level of detail, a stack trace looks something
like this:

Exception! Mayhem! and then ...

thrown at file Exception/Lite.t, line 307
in main::weKnowBetterThanYou, pid=24986, tid=1
@_=('ARRAY(0x83a8a90)'
,'rot, rot, rot'
,'Wikerson brothers'
,'triculous tripe'
,'There will be no more talking to hoos who are not!'
,'black bottom birdie'
,'from the three billionth flower'
,'Mrs Tucanella returns with uncles and cousins'
,'sound off! sound off! come make yourself known!'
,'Apartment 12J'
,'Jo Jo the young lad'
,'the whole world was saved by the smallest of all'
)
reached via file Exception/Lite.t, line 281
in main::notAWhatButAWho
@_=()
reached via file Exception/Lite.t, line 334 in main::__ANON__
@_=()
reached via file Exception/Lite.t, line 335 in <package: main>
@ARGV=()

Triggered by...
Exception! Horton hears a hoo!
rethrown at file Exception/Lite.t, line 315

thrown at file Exception/Lite.t, line 316
in main::horton, pid=24986, tid=1
@_=('15th of May'
,'Jungle of Nool'
,'a small speck of dust on a small clover'
,'a person's a person no matter how small'
)
reached via file Exception/Lite.t, line 310 in main::hoo
@_=('Dr Hoovey'
,'hoo-hoo scope'
,'Mrs Tucanella'
,'Uncle Nate'
)
reached via file Exception/Lite.t, line 303
in main::weKnowBetterThanYou
@_=('ARRAY(0x83a8a90)'
,'rot, rot, rot'
,'Wikerson brothers'
,'triculous tripe'
,'There will be no more talking to hoos who are not!'
,'black bottom birdie'
,'from the three billionth flower'
,'Mrs Tucanella returns with uncles and cousins'
,'sound off! sound off! come make yourself known!'
,'Apartment 12J'
,'Jo Jo the young lad'
,'the whole world was saved by the smallest of all'
)


=over

=item *

lines begining with "thrown" indicate a line where a new exception
was thrown. If an exception was chained, there might be multiple
such lines.

=item *

lines beginning with "reached via" indicate the path travelled
I<down> to the point where the exception was thrown. This is the
code that was excuted before the exception was triggered.

=item *

lines beginning with "rethrown at" indicate the path travelled
I<up> the stack by the exception I<after> it was geenerated. Each
line indicates a place where the exception was caught and rethrown.

=item *

lines introduced with "Triggered by" are exceptions that were
chained together. The original exception is the last of the
triggered exceptions. The original line is the "thrown" line
for the original exception.

=item *

C<@_> and <C@ARGV> below a line indicates what is left of the
parameters passed to a method, function or entry point routine.
In ideal circumstances they are the parameters passed to the
subroutine mentioned in the line immediately above C<@_>. In
reality, they can be overwritten or shifted away between the
point when the subroutine started and the line was reached.

Note: if you use L<Getopt::Long> to process C<@ARGV>, C<@ARGV>
will be empty reduced to an empty array. If this bothers you, you
can localize <@ARGV> before calling C<GetOptions>, like this:

my %hARGV;
{
local @ARGV = @ARGV;
GetOptions(\%hARGV,...);
}

=item *

pid is the process id where the code was running

=item *

tid is the thread id where the code was running

=back

=head1 SPECIAL TOPICS

=head2 Localization of error messages

Rather than treat the error message and properties as entirely
separate entities, it gives you the option to define a format string
that will take your property values and insert them automatically
into your message. Thus when you generate an exception, you can
specify only the properties and have your message automatically
generated without any need to repeat the property values in messy
C<sprintf>'s that clutter up your program.

One can localize from the very beginning when one declares the
class or later on after the fact if you are dealing with legacy
software or developing on an agile module and only implementing
what you need now.

To localize from the get-go:

# myLookupSub returns the arguments to declareException
# e.g. ('CopyError', [ 'On ne peut pas copier de %s a %s'
, qw(from to)])

declareExceptionClass( myLookupSub('CopyError', $ENV{LANG}) );


# .... later on, exception generation code doesn't need to
# know or care about the language. it just sets the properties


# error message depends on locale:
# en_US: 'Cannot copy A.txt to B.txt'
# fr_FR: 'On ne peut pas copier de A.txt a B.txt'
# de_DE: 'Kann nicht kopieren von A.txt nach B.txt'

die 'CopyError'->new(from => 'A.txt', to => 'B.txt');


Another alternative if you wish to localize from the get-go is
to pass a code reference instead of a format rule array. In this
case, C<Exception::Lite> will automatically pass the class name
to the subroutine and retrieve the value returned.


# anothherLookupSub has parameters ($sClass) and returns
# a format array, for example:
#
# %LOCALE_FORMAT_HASH = (
# CopyError => {
# en_US => ['Cannot copy %s to %s', qw(from to)]
# ,fr_FR => ['On ne peut pas copier de %s a %s', qw(from to)]
# ,de_DE => ['Kann nicht kopieren von %s nach %s''
# , qw(from to)]
#
# AddError => ...
# );
#
# sub anotherLookupSub {
# my ($sClass) = @_;
# my $sLocale = $ENV{LANG}
# return $LOCALE_FORMAT_HASH{$sClass}{$sLocale};
# }
#

declareExceptionClass('CopyError', &anotherLookupSub);
declareExceptionClass('AddError', &anotherLookupSub);


# error message depends on locale:
# en_US: 'Cannot copy A.txt to B.txt'
# fr_FR: 'On ne peut pas copier de A.txt a B.txt'
# de_DE: 'Kann nicht kopieren von A.txt nach B.txt'

die CopyError->new(from => 'A.txt', to => 'B.txt');
die AddError->new(path => 'C.txt');


If you need to put in localization after the fact, perhaps for a
new user interface you are developing, the design pattern might
look like this:

# in the code module you are retrofitting would be an exception
# that lived in a single language world.

declareExceptionClass('CopyError'
['Cannot copy %s to %s', [qw(from to)]);


# in your user interface application.

if (isException($e, 'CopyError') && isLocale('fr_FR')) {
my $sFrom = $e->getProperty('from');
my $sTo = $e->getProperty('to');
warn sprintf('On ne peut pas copier de %s a %s', $sFrom,$sTo);
}

=head2 Controlling verbosity and stack tracing

You don't need to print out the fully verbose stack trace and in
fact, by default you won't. The default setting, prints out
only what called what. To make it easier to see what called what,
it leaves out all of the dumps of C<@_> and C<@ARGV>.

If you want more or less verbosity or even an entirely different
trace, C<Exception::Lite> is at your sevice. It provides a variety
of options for controlling the output of the exception:

* Adjusting the level of debugging information when an exception is
thrown by setting C<$Exception::Lite::STRINGIFY>
in the program or C<-mException::Lite=STRINGIFY=level> on the
command line. This can be set to either a verbosity level or to
an exception stringification routine of your own choosing.

* Control which stack frames are displayed by setting
C<$Exception::Lite::FILTER>. By default, only calls within named
and anonymous subroutines are displayed in the stack trace. Perl
sometimes creates frames for blocks of code within a subroutine.
These are omitted by default. If you want to see them, you can
turn filterin off. Alternatively you can set up an entirely
custon stack filtering rule by assigning a code reference to
C<$Exception::Lite::FILTER>.

* By default, exceptions store and print a subset of the data
available for each stack frame. If you would like to display
richer per-frame information, you can do that too. See below
for details.

=head3 Verbosity level

The built-in rules for displaying exceptions as strings offer five
levels of detail.

* 0: Just the error message

* 1: the error message and the file/line number where it occured
along with pid and tid.

* 2: the error message and the calling sequence from the point where
the exception was generated to the package or script entry point
The calling sequence shows only file, line number and the name
of the subroutine where the exception was generated. It is not
cluttered with parameters, making it easy to scan.

* 3: similar to 2, except that propagation and chained exceptions
are also displayed.

* 4: same as 3, except that the state of C<@_> or C<@ARGV> at the
time the exception was thrown is also displayed. usually this
is the parameters that were passed in, but it may include several
leading C<undef> if C<shift> was used to process the parameter
list.

Here are some samples illustrating different level of debugging
information and what happens when the filter is turned off

#---------------------------------------------------
#Sample exception STRINGIFY=0 running on thread 5
#---------------------------------------------------

Exception! Mayhem! and then ...

#---------------------------------------------------
#Sample exception STRINGIFY=1 running on thread 5
#---------------------------------------------------

Exception! Mayhem! and then ...
at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=5

#---------------------------------------------------
#Sample exception STRINGIFY=2 running on thread 4
#---------------------------------------------------

Exception! Mayhem! and then ...
at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=4
via file Exception/Lite.t, line 281 in main::notAWhatButAWho
via file Exception/Lite.t, line 373 in main::__ANON__
via file Exception/Lite.t, line 374 in <package: main>

#---------------------------------------------------
#Sample exception STRINGIFY=3 running on thread 3
#---------------------------------------------------

Exception! Mayhem! and then ...

thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=3
reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
reached via file Exception/Lite.t, line 362 in main::__ANON__
reached via file Exception/Lite.t, line 363 in <package: main>

Triggered by...
Exception! Horton hears a hoo!
rethrown at file Exception/Lite.t, line 315

thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=3
reached via file Exception/Lite.t, line 310 in main::hoo
reached via file Exception/Lite.t, line 303 in main::weKnowBetterThanYou

#---------------------------------------------------
#Sample exception STRINGIFY=3 running on thread 2
#FILTER=OFF (see hidden eval frames)
#---------------------------------------------------

Exception! Mayhem! and then ...

thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=2
reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
reached via file Exception/Lite.t, line 348 in (eval)
reached via file Exception/Lite.t, line 348 in main::__ANON__
reached via file Exception/Lite.t, line 350 in (eval)
reached via file Exception/Lite.t, line 350 in <package: main>

Triggered by...
Exception! Horton hears a hoo!
rethrown at file Exception/Lite.t, line 315

thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=2
reached via file Exception/Lite.t, line 310 in (eval)
reached via file Exception/Lite.t, line 315 in main::hoo
reached via file Exception/Lite.t, line 303 in (eval)
reached via file Exception/Lite.t, line 305 in main::weKnowBetterThanYou

#---------------------------------------------------
#Sample exception STRINGIFY=4 running on thread 1
#FILTER=ON
#---------------------------------------------------

Exception! Mayhem! and then ...

thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=1
@_=('ARRAY(0x83a8a90)'
,'rot, rot, rot'
,'Wikerson brothers'
,'triculous tripe'
,'There will be no more talking to hoos who are not!'
,'black bottom birdie'
,'from the three billionth flower'
,'Mrs Tucanella returns with Wikerson uncles and cousins'
,'sound off! sound off! come make yourself known!'
,'Apartment 12J'
,'Jo Jo the young lad'
,'the whole world was saved by the tiny Yopp! of the smallest of all'
)
reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
@_=()
reached via file Exception/Lite.t, line 334 in main::__ANON__
@_=()
reached via file Exception/Lite.t, line 335 in <package: main>
@ARGV=()

Triggered by...
Exception! Horton hears a hoo!
rethrown at file Exception/Lite.t, line 315

thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=1
@_=('15th of May'
,'Jungle of Nool'
,'a small speck of dust on a small clover'
,'a person's a person no matter how small'
)
reached via file Exception/Lite.t, line 310 in main::hoo
@_=('Dr Hoovey'
,'hoo-hoo scope'
,'Mrs Tucanella'
,'Uncle Nate'
)
reached via file Exception/Lite.t, line 303 in main::weKnowBetterThanYou
@_=('ARRAY(0x83a8a90)'
,'rot, rot, rot'
,'Wikerson brothers'
,'triculous tripe'
,'There will be no more talking to hoos who are not!'
,'black bottom birdie'
,'from the three billionth flower'
,'Mrs Tucanella returns with Wikerson uncles and cousins'
,'sound off! sound off! come make yourself known!'
,'Apartment 12J'
,'Jo Jo the young lad'
,'the whole world was saved by the tiny Yopp! of the smallest of all'
)


=head3 Custom stringification subroutines

The custom stringification subroutine expects one parameter, the
exception to be stringified. It returns the stringified form of
the exception. Here is an example of a fairly silly custom
stringification routine that just prints out the chained messages
without any stack trace:

$Exception::Lite::STRINGIFY = sub {
my $e=$_[0]; # exception is sole input parameter
my $sMsg='';
while ($e) {
$sMsg .= $e->getMessage() . "\n";
$e= $e->getChained();
}
return $sMsg; # return string repreentation of message
};

=head3 Adding information to the stack trace

By default, each frame of the stack trace contains only the file,
line, containing subroutine, and the state of C<@_> at the time
C<$sFile>,C<$iLine> was reached.

If your custom subroutine needs more information about the stack
than C<Exception::Lite> normally provides, you can change the
contents of the stack trace by assigning a custom filter routine
to C<$Exception::Lite::FILTER>.

The arguments to this subroutine are:


($iFrame, $sFile, $iLine $sSub, $aArgs, $iSubFrame, $iLineFrame)

where

* C<$sFile> is the file of the current line in that frame

* C<$iLine> is the line number of current line in that frame

* C<$sSub> is the name of the subroutine that contains C<$sFile> and
C<$iLine>

* C<$aArgs> is an array that holds the stringified value of each
member of @_ at the time the line at C<$sFile>, C<$sLine> was
called. Usually, this is the parameters passed into C<$sSub>,
but may not be.

* C<$iSubFrame> is the stack frame that provided the name of the sub
and the contents of $aArgs.

* C<$iLineFrame> is the stack frame that provided the file and line
number for the frame.

Please be aware that each line of the stack trace passed into the
filter subroutine is a composite drawn from two different frames of
the Perl stack trace, C<$iSubFrame> and C<$iLineFrame>. This
composition is necessary because the Perl stack trace contains the
subroutine that was called at C<$sFile>, C<$iLine> rather than the
subroutine that I<contains> C<$sFile>,C<$iLine>.

The subroutine returns 0 or any other false value if the stack frame
should be omitted. It returns to 1 accept the default stack frame as
is. If it accepts the stack frame but wants to insert extra data
in the frame, it returns
C<[$sFile,$iLine,$sSub,$aArgs, $extra1, $extra2, ...]>

The extra data is always placed at the end after the C<$aArgs>
member.

=head3 Stack trace filtering

To avoid noise, by default, intermediate frames that are associated
with a block of code within a subroutine other than an anonymous
sub (e.g. the frame created by C<eval {...} or do {...} >) are
omitted from the stack trace.

These omissions add to readability for most debugging purposes.
In most cases one just wants to see which subroutine called which
other subroutine. Frames created by eval blocks don't provide
useful information for that purpose and simply clutter up the
debugging output.

However, there are situations where one either wants more or less
stack trace filtering. Stack filtering can turned on or off or
customized by setting C<$Exception::Lite::FILTER> to any of the
following values:

Normally the filtering rule is set at the start of the program or
via the command line. It can also be set anywhere in code, with one
caveat: an error handling block.

=over

=item 0

Turns all filtering off so that you see each and every frame
in the stack trace.

=item 1

Turns on filtering of eval frames only (default)

=item C<[ regex1, regex2, ... ]>

A list of regexes. If the fully qualified subroutine name matches
any one of these regular expressions it will be omitted from the
stack trace.

=item C<$regex>

A single regular expression. If the fully qualified subroutine name
matches this regular expression, it will be omitted from the stack
trace.

=item C<$codeReference>

The address of a named or anonymous routine that returns a boolean
value: true if the frame should be includeed, false if it should be
omitted. For parameters and return value of this subroutine see
L</Adding information to the stack trace>.


=back

If filtering strategies change and an exception is chained, some of
its stack frames might be lost during the chaining process if the
filtering strategy that was in effect when the exception was
generated changes before it is chained to another exception.


=head2 Subclassing

To declare a subclass with custom data and methods, use a three step
process:

=over

=item *

choose an exception superclass. The choice of superclass follows
the rule, "like gives birth to like". Exception superclasses that
have formats must have a superclass that also takes a format.
Exception subclasses that have no format, must use an exception.

=item *

call C<declareExceptionClass> with its C<$bCustom> parameter set
to 1

=item *

define a C<_new(...)> method (note the leading underscore _) and
subclass specific methods in a block that sets the package to
the subclass package.

=back


When the C<$bCustom> flag is set to true, it might be best to think
of C<declareExceptionClass> as something like C<use base> or
C<use parent> except that there is no implicit BEGIN block. Like
both these methods it handles all of the setup details for the
class so that you can focus on defining methods and functionality.

Wnen C<Exception::Lite> sees the C<$bCustom> flag set to true, it
assumes you plan on customizing the class. It will set up inhertance,
and generate all the usual method definition for an C<Exception::Lite>
class. However, on account of C<$bCustom> being true, it will add a
few extra things so that and your custom code can play nicely
together:

=over

=item *

a special hash reserved for your subclsses data. You can get
access to this hash by calling C<_p_getSubclassData()>. You are
free to add, change, or remove entries in the hash as needed.

=item *

at the end of its C<new()> method, it calls
C<< $sClass->_new($self) >>. This is why you must define a C<_new()>
method in your subclass package block. The C<_new> method is
responsible for doing additional setup of exception data. Since
this method is called last it can count on all of the normally
expected methods and data having been set up, including the
stack trace and the message generated by the classes format rule
(if there is one).

=back

For example, suppose we want to define a subclass that accepts
formats:

#define a superclass that accepts formats

declareExceptionClass('AnyError'
, ['Unexpected exception: %s','exception']);


# declare Exception subclass

declareExceptionClass('TimedException', 'AnyError', $aFormatData,1);
{
package TimedException;

sub _new {
my $self = $_[0]; #exception object created by Exception::Lite

# do additional setup of properties here
my $timestamp=time();
my $hMyData = $self->_p_getSubclassData();
$hMyData->{when} = time();
}

sub getWhen {
my $self=$_[0];
return $self->_p_getSubclassData()->{when};
}
}


Now suppose we wish to extend our custom class further. There is
no difference in the way we do things just because it is a subclass
of a customized C<Exception::Lite> class:

# extend TimedException further so that it
#
# - adds two additional bits of data - the effective gid and uid
# at the time the exception was thrown
# - overrides getMessage() to include the time, egid, and euid

declareExceptionClass('SecureException', 'TimedException'
, $aFormatData,1);
{
package TimedException;

sub _new {
my $self = $_[0]; #exception object created by Exception::Lite

# do additional setup of properties here
my $timestamp=time();
my $hMyData = $self->_p_getSubclassData();
$hMyData->{euid} = $>;
$hMyData->{egid} = $);
}

sub getEuid {
my $self=$_[0];
return $self->_p_getSubclassData()->{euid};
}
sub getEgid {
my $self=$_[0];
return $self->_p_getSubclassData()->{egid};
}
sub getMessage {
my $self=$_[0];
my $sMsg = $self->SUPER::getMessage();
return sprintf("%s at %s, euid=%s, guid=%s", $sMsg
, $self->getWhen(), $self->getEuid(), $self->getGuid());
}
}

=head2 Converting other exceptions into Exception::Lite exceptions

If you decide that you prefer the stack traces of this package, you
can temporarily force all exceptions to use the C<Exception::Lite>
stack trace, even those not generated by your own code.

There are two ways to do this:

* production code: chaining/wrapping

* active debugging: die/warn handlers


=head3 Wrapping and chaining

The preferred solution for production code is wrapping and/or
chaining the exception. Any non-string exception, even one
of a class not created by C<Exception::Lite> can be chained
to an C<Exception::Lite> exception.

To chain a string exception, you first need to wrap it in
an exception class. For this purpose you can create a special
purpose class or use the generic exception class provided by
the C<Exception::Lite> module: C<Exception::Lite::Any>.

If you don't want to chain the exception, you can also just
rethrow the wrapped exception, as is. Some examples:

#-----------------------------------------------------
# define some classes
#-----------------------------------------------------

# no format rule
declareExceptionClass('HouseholdRepairNeeded');

# format rule
declareExceptionClass('ProjectDelay'
, ['The project was delayed % days', qw(days)]);

#-----------------------------------------------------
# chain and/or wrap some exceptins
#-----------------------------------------------------

eval {
.... some code here ...
return 1;
} or do {

my $e=$@;
if (Exception::Lite::isChainable($e)) {
if ("$e" =~ /project/) {

# chain formatted message
die 'ProjectDelay'->new($e, days => 3);

} elsif ("$e" =~ /water pipe exploded/) {

# chain unformatted message
die 'HouseholdRepairNeeded'->new($e, 'Call the plumber');

}
} elsif ($e =~ 'repairman') { #exception is a string

# wrapping a scalar exception so it has the stack trace
# up to this point, but _no_ chaining
#
# since the exception is a scalar, the constructor
# of a no-format exception class will treat the first
# parameter as a message rather than a chained exception

die 'HouseholdRepairNeeded'->new($e);

} else {

# if we do want to chain a string exception, we need to
# wrap it first in an exception class:

my $eWrapped = Exception::Lite::Any->new($e);
die 'HouseholdRepairNeeded'
->new($eWrapped, "Call the repair guy");
}
}

=head3 Die/Warn Handlers

Die/Warn handlers provide a quick and dirty way to at Exception::Lite
style stack traces to all warnings and exceptions. However,
it should ONLY BE USED DURING ACTIVE DEBUGGING. They should never
be used in production code. Setting these handlers
can interfere with the debugging style and techiniques of other
programmers and that is not nice.

However, so long as you are actiely debugging, setting a die or
warn handler can be quite useful, especially if a third party module
is generating an exception or warning and you have no idea where it
is coming from.

To set a die handler, you pass your desired stringify level or
code reference to C<onDie>:

Exception::Lite::onDie(4);

This is roughly equivalent to:

$SIG{__DIE__} = sub {
$Exception::Lite::STRINGIFY=4;
warn 'Exception::Lite::Any'->new('Unexpected death:'.$_[0])
unless ($^S || Exception::Lite::isException($_[0]));
};

To set a warning handler, you pass your desired stringify level or
code reference to C<onWarn>:

Exception::Lite::onWarn(4);

This is roughly equivalent to:

$SIG{__WARN__} = sub {
$Exception::Lite::STRINGIFY=4;
print STDERR 'Exception::Lite::Any'->new("Warning: $_[0]");
};

Typically these handlers are placed at the top of a test script
like this:

use strict;
use warnings;
use Test::More tests => 25;

use Exception::Lite;
Exception::Lite::onDie(4);
Exception::Lite::onWarn(3);

... actual testing code here ...



=head1 WHY A NEW EXCEPTION CLASS

Aren't there enough already? Well, no. This class differs from
existing classes in several significant ways beyond "lite"-ness.

=head2 Simplified integration of properties and messages

C<Exception::Lite> simplifies the creation of exceptions by
minimizing the amount of metadata that needs to be declared for
each exception and by closely integrating exception properties
and error messages. Though there are many exception modules
that let you define message and properties for exceptions, in
those other modules you have to manually maintain any connection
between the two either in your code or in a custom subclass.

In L<Exception::Class|Exception::Class>, for example, you have to
do something like this:

#... at the start of your code ...
# notice how exception definition and message format
# string constant are in two different places and need
# to be manually coordinated by the programmer.

use Exception::Class {
'Exception::Copy::Mine' {
fields => [qw(from to)];
}
# ... lots of other exceptions here ...
}
my $MSG_COPY='Could not copy A.txt to B.txt";

... later on when you throw the exception ...

# notice the repetition in the use of exception
# properties; the repetition is error prone and adds
# unnecessary extra typing

my $sMsg = sprintf($MSG_COPY, 'A.txt', 'B.txt');
Exception::Copy::Mine->throw(error => $sMsg
, from => 'A.txt'
, to => 'B.txt');


C<Exception::Lite> provides a succinct and easy to maintain
method of declaring those same exceptions

# the declaration puts the message format string and the
# class declaration together for the programmer, thus
# resulting in less maintenence work

declareExceptionClass("Exception::Mine::Copy"
, ["Could not copy %s to %s", qw(from, to) ]);


.... some where else in your code ...


# there is no need to explicitly call sprintf or
# repetitively type variable names, nor even remember
# the order of parameters in the format string or check
# for undefined values. Both of these will produce
# the same error message:
# "Could not copy A.txt to B.txt"

die "Exception::Mine:Copy"->new(from =>'A.txt', to=>'B.txt');
die "Exception::Mine:Copy"->new(to =>'B.txt', from=>'A.txt');

# and this will politely fill in 'undef' for the
# property you leave out:
# "Could not copy A.txt to <undef>"

die "Exception::Mine::Copy"->new(from=>'A.txt');


=head2 More intelligent stack trace

The vast majority, if not all, of the exception modules on CPAN
essentially reproduce Carp's presentation of the stack trace. They
sometimes provide parameters to control the level of detail, but
make only minimal efforts, if any, to improve on the quality of
debugging information.

C<Exception::Lite> improves on the traditional Perl stack trace
provided by Carp in a number of ways.

=over

=item *

Error messages are shown in full and never truncated (a problem with
C<Carp::croak>.

=item *

The ability to see a list of what called what without the clutter
of subroutine parameters.

=item *

The ability to see the context of a line that fails rather than
a pinhole snapshot of the line itself. Thus one sees
"at file Foo.pm, line 13 in sub doTheFunkyFunk" rather
than the contextless stack trace line displayed by nearly every,
if not all Perl stacktraces, including C<Carp::croak>:
"called foobar(...) at line 13 in Foo.pm".
When context rather than line snapshots
are provided, it is often enough simply to scan the list of what
called what to see where the error occurred.

=item *

Automatic filtering of stack frames that do not show the actual
Flow from call to call. Perl internally creates stack frames for
each eval block. Seeing these in the stack trace make it harder
to scan the stack trace and see what called what.

=item *

The automatic filtering can be turned off or, alternatively
customized to include/exclude arbitrary stack frames.

=item *

One can chain together exceptions and then print out what exception
triggered what other exception. Sometimes what a low level module
considers important about an exception is not what a higher level
module considers important. When that happens, the programmer can
create a new exception with a more relevant error message that
"remembers" the exception that inspired it. If need be, one can
see the entire history from origin to destination.

=back

The "traditional" stack trace smushes together all parameters into
a single long line that is very hard to read. C<Exception::Lite>
provides a much more readable parametr listing:

=over

=item *

They are displayed one per line so that they can be easily read
and distinguished one from another

=item *

The string value <i>and</i> the normal object representation is
shown when an object's string conversion is overloaded. That way
there can be no confusion about whether the actual object or a
string was passed in as a parameter.

=item *

It doesn't pretend that these are the parameters passed to the
subroutine. It is impossible to recreate the actual values in
the parameter list because the parameter list for any sub is
just C<@_> and that can be modified when a programmer uses shift
to process command line arguments. The most Perl can give (through
its DB module) is the way C<@_> looked at the time the next frame
in the stack was set up. Instead of positioning the parameters
as if they were being passed to the subroutine, they are listed
below the stacktrace line saying "thrown at in line X in
subroutine Y". In reality, the "parameters" are the value of
@_ passed to subroutine Y (or @ARGV if this is the entry point),
or what was left of it when we got to line X.

=item

A visual hint that leading C<undef>s in C<@_> or C<@ARGV> may be
the result of shifts rather than a heap of C<undef>s passed into
the subroutine. This lets the programmer focus on the code, not
on remembering the quirks of Perl stack tracing.

=back

=head1 CLASS REFERENCE

=head2 Class factory methods

=head3 C<declareExceptionClass>

declareExceptionClass($sClass);
declareExceptionClass($sClass, $sSuperclass);
declareExceptionClass($sClass, $sSuperclass, $bCustom);

declareExceptionClass($sClass, $aFormatRule);
declareExceptionClass($sClass, $sSuperclass, $aFormatRule);
declareExceptionClass($sClass, $sSuperclass, $aFormatRule
, $bCustom);

Generates a lightweight class definition for an exception class. It
returns the name of the created class, i.e. $sClass.

=over

=item C<$sClass>

The name of the class (package) to be created. Required.

Any legal Perl package name may be used, so long as it hasn't
already been used to define an exception or any other class.

=item C<$sSuperclass>

The name of the superclass of C<$sClass>. Optional.

If missing or undefed, C<$sClass> will be be a base class
whose only superclass is C<UNIVERSAL>, the root class of all Perl
classes. There is no special "Exception::Base" class that all
exceptions have to descend from, unless you want it that way
and choose to define your set of exception classes that way.

=item C<$aFormatRule>

An array reference describing how to use properties to construct
a message. Optional.

If provided, the format rule is essential the same parameters as
used by sprintf with one major exception: instead of using actual
values as arguments, you use property names, like this:

# insert value of 'from' property in place of first %s
# insert value of 'to' property in place of first %s

[ 'Cannot copy from %s to %s, 'from', 'to' ]

When a format rule is provided, C<Exception::Lite> will auto-generate
the message from the properties whenever the properties are set or
changed. Regeneration is a lightweight process that selects property
values from the hash and sends them to C<sprintf> for formatting.

Later on, when you are creating exceptions, you simply pass in the
property values. They can be listed in any order and extra properties
that do not appear in the message string can also be provided. If
for some reason the value of a property is unknown, you can assign it
C<undef> and C<Exception::Lite> will politely insert a placeholder
for the missing value. All of the following are valid:


# These all generate "Cannot copy A.txt to B.txt"

$sClass->new(from => 'A.txt', to => 'B.txt');
$sClass->new(to => 'B.txt', from => 'A.txt');
$sClass->new(to => 'B.txt', from => 'A.txt'
, reason => 'source doesn't exist'
, seriousness => 4
);
$sClass->new(reason => 'source doesn't exist'
, seriousness => 4
, to => 'B.txt', from => 'A.txt'
);

# These generate "Cannot copy A.txt to <undef>"

$sClass->new(from => 'A.txt');
$sClass->new(from => 'A.txt', to => 'B.txt');

=item C<$bCustom>

True if the caller intends to add custom methods and/or a custom
constructor to the newly declared class. This will force the
L<Excepton::Lite> to generate some extra methods and data so
that the subclass can have its own private data area in the class.
See L</Subclassing> for more information.


=back

=head2 Object construction methods

=head3 C<new>

# class configured for no generation from properties

$sClass->new($sMsg);
$sClass->new($sMsg,$prop1 => $val1, ....);
$sClass->new($e);
$sClass->new($e, $sMsg);
$sClass->new($e, $sMsg,$prop1 => $val1, ....);

# class configured to generate messages from properties
# using a per-class format string

$sClass->new($prop1 => $val1, ....);
$sClass->new($e, $prop1 => $val1, ....);


Creates a new instance of exception class C<$sClass>. The exception
may be independent or chained to the exception that triggered it.

=over

=item $e

The exception that logically triggered this new exception.
May be omitted or left undefined. If defined, the new exception is
considered chained to C<$e>.

=item $sMsg

The message text, for classes with no autogeneration from properties,
that is, classes declared like

declareExceptionClass($sClass);
declareExceptionClass($sClass, $sSuperclass);

In the constructor, C< $sClass->new($e) >>, the message defaults to
the message of C<$e>. Otherwise the message is required for any
class that id declared in the above two ways.

=item $prop1 => $val1

The first property name and its associated value. There can be
as many repetitions of this as there are properties. All types
of exception classes may have property lists.

=back

If you have chosen to have the message be completely independent
of properties:

declareExceptionClass('A');

# unchained exception - print output "Hello"

my $e1 = A->new("Hello", importance => 'small', risk => 'large');
print "$e1\n";

# chained exception - print output "Hello"

my $e2 = A->new($e1,'Goodbye');

$e2->getChained(); # returns $e1
print $e1->getMessage(); # outputs "Goodbye"
print $e1; # outputs "Goodbye"
print $e2->getChained()->getMessage(); # outputs "Hello"


If you have chosen to have the message autogenerated from properties
your call to C<new> will look like this:

$sFormat ='the importance is %s, but the risk is %s';
declareExceptionClass('B', [ $sFormat, qw(importance risk)]);


# unchained exception

my $e1 = B->new(importance=>'small', risk=>'large');

$e1->getChained(); # returns undef
print "$e1\n"; # outputs "The importance is small, but the
# risk is large"

# chained exception

$e2 = B->new($e1, importance=>'yink', risk=>'hooboy');
$e2->getChained(); # returns $e1
"$e2" # evaluates to "The importance is yink, but
# the risk is hooboy"
$e2->getMessage() # same as "$e2"
$e2->getChained()->getMessage(); # same as "$e1"



=head2 Object methods

=head3 C<getMessage>

$e->getMessage();

Returns the messsage, i.e. the value displayed when this exception
is treated as a string. This is the value without line numbers
stack trace or other information. It includes only the format
string with the property values inserted.

=head3 C<getProperty>

$e->getProperty($sName);

Returns the property value for the C<$sName> property.

=head3 C<isProperty>

$e->isProperty($sName)

Returns true if the exception has the C<$sName> property, even if
the value is undefined. (checks existance, not definition).

=head3 C<getPid>

$e->getPid();

Returns the process id of the process where the exception was
thrown.

=head3 C<getPackage>

$e->getPackage();

Returns the package contining the entry point of the process, i.e.
the package identified at the top of the stack.


=head3 C<getTid>

Returns the thread where the exception was thrown.

$e->getTid();

=head3 C<getStackTrace>

$e->getStackTrace();

Returns the stack trace from the point where the exception was
thrown (frame 0) to the entry point (frame -1). The stack trace
is structured as an array of arrays (AoA) where each member array
represents a single lightweight frame with four data per frame:

[0] the file
[1] the line number within the file
[2] the subroutine where the exception was called. File and
line number will be within this subroutine.
[3] a comma delimited string containing string representations
of the values that were stored in @_ at the time the
exception was thrown. If shift was used to process the
incoming subroutine arguments, @_ will usually contain
several leading undefs.

For more information about each component of a stack frame, please
see the documentation below for the following methods:

* C<getFile> - explains what to expect in [0] of stack frame

* C<getLine> - explains what to expect in [1] of stack frame

* C<getSubroutine> - explains what to expect in [2] of stack frame

* C<getArgs> - explains what to expect in [3] of stack frame

The frame closest to the thrown exception is numbered 0. In fact
frame 0, stores information about the actual point where the exception
was thrown.


=head3 C<getFrameCount>

$e->getFrameCount();

Returns the number of frames in the stack trace.

=head3 C<getFile>

$e->getFile(0); # gets frame where exception was thrown
$e->getFile(-1); # gets entry point frame

$e->getFile(); # short hand for $e->getFile(0)
$e->getFile($i);

Without an argument, this method returns the name of the file where
the exception was thrown. With an argument it returns the name of
the file in the C<$i>th frame of the stack trace.

Negative values of C<$i> will be counted from the entry point with
C<-1> representing the entry point frame, C<-2> representing the
first call made within the script and so on.

=head3 C<getLine>

$e->getLine(0); # gets frame where exception was thrown
$e->getLine(-1); # gets entry point frame

$e->getLine(); # short hand for $e->getLine(0)
$e->getLine($i);

Without an argument, this method returns the line number where the
exception was thrown. With an argument it returns the line number
in the C<$i>th frame of the stack trace.

Negative values of C<$i> will be counted from the entry point with
C<-1> representing the entry point frame, C<-2> representing the
first call made within the script and so on.

=head3 C<getSubroutine>

$e->getSubroutine(0); # gets frame where exception was thrown
$e->getSubroutine(-1); # gets entry point frame

$e->getSubroutine(); # short hand for $e->getSubroutine(0)
$e->getSubroutine($i);

Without an argument, this method returns the name of the subroutine
where this exception was created via C<new(...)>. With an argument
it returns the value of the subroutine (or package entry point) in
the C<$i>th frame of the stack trace.

Negative values of C<$i> will be counted from the entry point with
C<-1> representing the entry point frame, C<-2> representing the
first call made within the script and so on.

Note: This is not the same value as returned by C<caller($i)>. C<caller> returns the name of the subroutine that was being called
at the time of death rather than the containing subroutine.

The subroutine name in array element [2] includes the package name
so it will be 'MyPackage::Utils::doit' and not just 'doit'. In the
entry point frame there is, of course, no containing subroutine so
the value in this string is instead the package name embedded in
the string "<package: packageName>".


=head3 C<getArgs>

$e->getArgs(0); # gets frame where exception was thrown
$e->getArgs(-1); # gets entry point frame

$e->getArgs(); # short hand for $e->getArgs(0)
$e->getArgs($i);

Without an argument, this method returns the value of C<@_> (or
C<@ARGV> for an entry point frame) at the time the exception was
thrown. With an argument it returns the name of
the file in the C<$i>th frame of the stack trace.

Negative values of C<$i> will be counted from the entry point with
C<-1> representing the entry point frame, C<-2> representing the
first call made within the script and so on.

@_, is the best approximation Perl provides for the arguments
used to call the subroutine. At the start of the subroutine it does
in fact reflect the parameters passed in, but frequently programmers
will process this array with the C<shift> operator which will set
leading arguments to C<undef>. The debugger does not cache the
oiginal value of @_, so all you can get from its stack trace is the
value at the time the exception was thrown, not the value when the
subroutine was entered.

=head3 C<getPropagation>

$e->getPropagation();

Returns an array reference with one element for each time this
exception was caught and rethrown using either Perl's own rethrow
syntax C<$@=$e; die;> or this packages: C<< die->rethrow() >>.

Each element of the array contains a file and line number where
the exception was rethrown:

[0] file where exception was caught and rethrown
[1] line number where the exception was caught and rethrown

Note: do not confuse the stack trace with propagation. The stack
trace is the sequence of calls that were made I<before> the
exception was thrown. The propagation file and line numbers
refer to where the exception was caught in an exception handling
block I<after> the exception was thrown.

Generally, bad data is the reason behind an exception. To see
where the bad data came from, it is generally more useful to
look at the stack and see what data was passed down to the point
where the exception was generated than it is to look at where
the exception was caught after the fact.

=head3 C<getChained>

my $eChained = $e->getChained();

Returns the chained exception, or undef if the exception is not
chained. Chained exceptions are created by inserting the triggering
exception as the first parameter to C<new(...)>.

# class level format
MyException1->new(reason=>'blahblahblah'); #unchained
MyException1->new($e, reason=>'blahblahblah'); #chained

# no format string
MyException1->new('blahblahblah'); #unchained
MyException1->new($e, reason=>'blahblahblah'); #chained


The chained exception can be a reference to any sort of data. It
does not need to belong to the same class as the new exception,
nor does it even have to belong to a class generated by
C<Exception::Lite>. Its only restriction is that it may not be
a scalar(string, number, ec). To see if an exception
may be chained you can call C<Exception::Lite::isChainable()>:

if (Exception::Lite::isChainable($e)) {
die MyException1->new($e, reason=>'blahblahblah');
} else {

# another alternative for string exceptions
my $eWrapper=MyWrapperForStringExceptions->new($e);
die MyException1->new($eWrapper, reason=>'blahblahblah');

# another alternative for string exceptions
die MyException1->new($eWrapper, reason=>"blahblahblah: $e");
}


=head3 C<rethrow>

$e->rethrow();
$e->rethrow($prop => $newValue); # format rule

$e->rethrow($newMsg, $p1 => $newValue); # no format rule
$e->rethrow(undef, $pl => $newValue); # no format rule
$e->rethrow($sNewMsg); # no format rule


Propagates the exception using the method (C<PROPAGATE>) as would
be called were one to use Perl's native 'rethrow' syntax,
C<$@=$e; die>.

The first form with no arguments simply rethrows the exception.
The remain formats let one override property values and/or update
the message. The argument list is the same as for C<new> except
that exceptions with no or object level format strings may have
an undefined message.

For class format exceptions, the message will automatically be
updated if any of the properties used to construct it have changed.

For exception classes with no formatting, property and message
changes are independent of each other. If C<$sMsg> is set to C<undef>
the properties will be changed and the message will be left alone.
If C<$sMsg> is provided, but no override properties are provided,
the message will change but the properties will be left untouched.

=head3 C<_p_getSubclassData>

Method for internal use by custom subclasses. This method retrieves
the data hash reserved for use by custom methods.


=head1 SEE ALSO

=head2 Canned test modules

Test modules for making sure your code generates the right
exceptions. They work with any OOP solution, even C<Exception::Lite>

* L<Test::Exception|Test::Exception> - works with any OOP solution

* L<Test::Exception::LessClever|Test::Exception::LessClever> - works
with any OOP solution

=head2 Alternate OOP solutions

=head3 L<Exception::Class|Exception::Class>

This module has a fair number of non-core modules. There are several
extension modules. Most are adapter classes that convert exceptions
produced by popular CPAN modules into Exception::Class modules:

* L<Exception::Class::Nested|Exception::Class::Nested> - changes
the syntax for declaring exceptions.

* L<MooseX::Error::Exception::Class|MooseX::Error::Exception::Class>
- converts Moose exceptions to
C<Exception::Class> instances.

* L<HTTP::Exception|HTTP::Exception> - wrapper around HTTP exceptions

* L<Mail::Log::Exceptions|Mail::Log::Exceptions> - wrapper around
Mail::Log exceptions

* L<Exception::Class::DBI|Exception::Class::DBI> - wrapper around
DBI exceptions

* L<Error::Exception|Error::Exception> - prints out exception
properties as part of exception stringification.

It takes a heavy approach to OOP, requiring all properties to be
predeclared. It also stores a lot of information about an exception,
not all of which is likely to be important to the average user, e.g.
pid, uid, guid and even the entire stack trace.

There is no support for auto-generating messages based on
properties.

For an extended discussion of C<Exception::Class>, see
L<http://www.drdobbs.com/web-development/184416129>.

=head3 L<Exception::Base|Exception::Base>

A light weight version of L<Exception::Class|Exception::Class>.
Uses only core modules but is fairly new and has no significant
eco-system of extensions (yet).
Like C<Exception::Class> properties must be explicitly declared and
there is no support for autogenerating messages based on properties.


=head3 L<Class::Throwable|Class::Throwable>

Another light weight version of L<Exception::Class|Exception::Class>.
Unlike C<Exception::Class> you can control the amount of system
state and stack trace information stored at the time an exception
is generated.

=head2 Syntactic sugar solutions

Syntactical sugar solutions allow java like try/catch blocks to
replace the more awkward C<die>, C<eval/do>, and C<$@=$e; die>
pattern. Take care in chosing these methods as they sometimes
use coding strategies known to cause problems:

=over

=item *

overriding signal handlers - possible interference with your own
code or third party module use of those handlers.

=item *

source code filtering - can shift line numbers so that the reported
line number and the actual line number may not be the same.

=item *

closures - there is a apparently a problem with nested closures
causing memory leaks in some versions of Perl (pre 5.8.4). This
has been since fixed since 5.8.4.

=back

Modules providing syntactic sugar include:

* L<Try::Catch|Try::Catch>

* L<Try::Tiny|Try::Tiny>

* C<Error|Error>

* L<Exception::Caught|Exception::Caught>

* L<Exception::SEH|Exception::SEH>

* C<Exception|Exception>

* L<Exception::Class::TryCatch|Exception::Class::TryCatch> - extension of L<Exception::Class|Exception::Class>

* L<Exception::Class::TCF|Exception::Class::TCF> - extension of L<Exception::Class|Exception::Class>


=head1 EXPORTS

No subroutines are exported by default. See the start of the synopsis
for optional exports.


=head1 AUTHOR

Elizabeth Grace Frank-Backman

=head1 COPYRIGHT

Copyright (c) 2011 Elizabeth Grace Frank-Backman.
All rights reserved.

=head1 LICENSE

This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
(2-2/2)