package Dancer::Exception::Base;
our $AUTHORITY = 'cpan:SUKRIA';
#ABSTRACT: the base class of all Dancer exceptions
$Dancer::Exception::Base::VERSION = '1.3513';
use strict;
use warnings;
use Carp;

use base qw(Exporter);

use Dancer::Exception;

use overload '""' => sub {
    my ($self) = @_;
    $self->message
      . ( $Dancer::Exception::Verbose ? $self->{_longmess} : $self->{_shortmess});
};

# string comparison is done without the stack traces
use overload 'cmp' => sub {
    my ($e, $f) = @_;
    ( ref $e && $e->isa(__PACKAGE__)
      ? $e->message : $e )
      cmp
    ( ref $f && $f->isa(__PACKAGE__)
      ? $f->message : $f )
};

# This is the base class of all exceptions

sub new {
    my $class = shift;
    my $self = bless { _raised_arguments => [],
                       _shortmess => '',
                       _longmess => '',
                     }, $class;
    $self->_raised_arguments(@_);
    return $self;
}

# base class has a passthrough message
sub _message_pattern { '%s' }

sub throw {
    my $self = shift;
    $self->_raised_arguments(@_);
    local $Carp::CarpInternal;
    local $Carp::Internal;
    $Carp::Internal{'Dancer'} ++;
    $Carp::CarpInternal{'Dancer::Exception'} ++;
    $self->{_shortmess} = Carp::shortmess;
    $self->{_longmess} = Carp::longmess;
    die $self;
}

sub rethrow { die $_[0] }

sub message {
    my ($self) = @_;
    my $message_pattern = $self->_message_pattern;
    my $message = sprintf($message_pattern, @{$self->_raised_arguments});
    return $message;
}

sub does {
    my $self = shift;
    my $regexp = join('|', map { '^' . $_ . '$'; } @_);
    (scalar grep { /$regexp/ } $self->get_composition) >= 1;
}

sub get_composition {
    my ($self) = @_;
    my $class = ref($self);

    my ($_recurse_isa, %seen);
    $_recurse_isa = sub {
        my ($class) = @_;
        $seen{$class}++
          and return;

        no strict 'refs';
        return $class, map { $_recurse_isa->($_) }
                      grep { /^Dancer::Exception::/ }
                          @{"${class}::ISA"};
        
    };
    grep { s/^Dancer::Exception::// } $_recurse_isa->($class);
}

sub _raised_arguments {
    my $self = shift;
    @_ and $self->{_raised_arguments} = [ @_ ];
    $self->{_raised_arguments};
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Dancer::Exception::Base - the base class of all Dancer exceptions

=head1 VERSION

version 1.3513

=head1 DESCRIPTION

Dancer::Exception::Base is the base class of all Dancer exception. All core
exceptions, and all custom exception registered using
C<Dancer::Exception::register_exception> inherits of
C<Dancer::Exception::Base>.

=head1 METHODS

=head2 throw

Throws an exception. It's what C<raise> (from L<Dancer::Exception>) uses. Any
arguments is set as raising parameters. You should not use this method
directly, but instead, use C<raise> from L<Dancer::Exception>.

B<Warning>, if you want to rethrow an exception, use C<rethrow>.

=head2 rethrow

Re-throw the exception, without touching its parameters. Useful if you've
caught and exception but don't want to handle it, and want to rethrow it.

  try { ... }
  catch {
    my ($e) = @_;
    $e->does('InvalidLogin')
      or $e->rethrow;
    ...
  };

=head2 does

Given an exception type, returns true if the exception is of the same type.

  try { raise InvalidLogin => 'foo'; }
  catch {
    my ($e) = @_;
    $e->does('InvalidLogin') # true
    ...
  };

It can receive more than one type, useful for composed exception, or checking
multiple types at once. C<does> performs a logical OR between them:

  try { raise InvalidPassword => 'foo'; }
  catch {
    my ($e) = @_;
    $e->does('InvalidLogin', 'InvalidPassword') # true
    ...
  };

=head2 get_composition

Returns the composed types of an exception. As every exception inherits of
Dancer::Exception::Base, the returned list contains at least 'Base', and the
exception class name.

B<Warning>, the result is a list, so you should call this method in list context.

  try { raise InvalidPassword => 'foo'; }
  catch {
    my ($e) = @_;
    my @list = $e->get_composition()
    # @list contains ( 'InvalidPassword', 'Base', ... )
  };

=head2 message

Computes and returns the message associated to the exception. It'll apply the
parameters that were set at throw time to the message pattern of the exception.

=head1 STRINGIFICATION

=head2 string overloading

All Dancer exceptions properly stringify. When evaluated to a string, they
return their message, concatenated with their stack trace (see below).

=head2 cmp overloading

The C<cmp> operator is also overloaded, thus all the string operations can be
done on Dancer's exceptions, as they will all be based on the overloaded C<cmp>
operator. Dancer exceptions will be compared B<without> their stacktraces.

=head1 STACKTRACE

Similarly to L<Carp>, Dancer exceptions stringification appends a string
stacktrace to the exception message.

The stacktrace can be a short one, or a long one. Actually the implementation
internally uses L<Carp>.

To enable long stack trace (for debugging purpose), you can use the global
variable C<Dancer::Exception::Verbose> (see below).

The short and long stacktrace snippets are stored within C<$self->{_shortmess}>
and C<$self->{_longmess}>. Don't touch them or rely on them, they are
internals, and will change soon.

=head1 GLOBAL VARIABLE

=head2 $Dancer::Exception::Verbose

When set to 1, exceptions will stringify with a long stack trace. This variable
is similar to C<$Carp::Verbose>. I recommend you use it like that:

  local $Dancer::Exception::Verbose;
  $Dancer::Exception::Verbose = 1;

All the L<Carp> global variables can also be used to alter the stacktrace
generation.

=head1 AUTHOR

Dancer Core Developers

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2010 by Alexis Sukrieh.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
