File Coverage

blib/lib/Exception/Class/TCF.pm
Criterion Covered Total %
statement 121 162 74.6
branch 48 76 63.1
condition 6 12 50.0
subroutine 22 34 64.7
pod 19 26 73.0
total 216 310 69.6


line stmt bran cond sub pod time code
1             package Exception::Class::TCF;
2             use Exception::Class (
3 1         10 'Exception::Class::TCF' => {
4             'isa' => 'Exception::Class::Base',
5             'fields' => ['Message']
6             }
7 1     1   2088 );
  1         31269  
8 1     1   715 use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
  1         4  
  1         4277  
9             require Exporter;
10             @ISA = qw(Exporter Exception::Class::Base);
11             @EXPORT = qw(&try &catch &throw &finally);
12             @EXPORT_OK = qw(&isThrowing &deactivate &handleWarn &handleDie &make);
13             $VERSION = '0.03';
14              
15             my $DEFAULT_UNCAUGHT = "Exception of type %s thrown but not caught";
16             my %PROTECTED = map { $_ => 1 } qw(Message);
17              
18             sub UNIVERSAL::throw (@) {
19 0     0 0 0 my ( $pack, $file, $line ) = caller;
20 0         0 warn "Parsing problem with throw at $file line $line.\n";
21 0         0 &Exception::Class::TCF::throw(@_)
22             }
23              
24             sub UNIVERSAL::make (@) {
25 0     0 0 0 my($pack,$file,$line) = caller;
26 0         0 warn "Parsing problem with throw at $file line $line.\n";
27 0         0 &Exception::Class::TCF::make(@_)
28             }
29              
30             sub UNIVERSAL::catch (@) {
31 0     0 0 0 &Exception::Class::TCF::catch(@_)
32             }
33              
34             sub isException {
35 27     27 0 49 my $class = shift;
36 27 50       100 $class = ref $class if ref $class;
37 27         49 &isBelow($class,'Exception::Class::TCF');
38             }
39              
40             sub isBelow {
41 65     65 0 84 my($class,$above) = @_;
42 65 100       714 $class->isa($above) || $class->isa('Exception::Class::TCF::'.$above);
43             }
44              
45             sub new {
46 31     31 1 44 my($class) = shift;
47 31 100       70 unshift @_,'Message' if @_ % 2;
48 31         59 my %args = @_;
49 31         176 my $self = $class->SUPER::new( 'Message' => $args{'Message'} );
50 31         15190 bless $self, $class;
51 31         95 for my $key ( keys %args ) {
52 4 50       30 if ( $key ne 'Message' ) {
53 0         0 $self->setFields( $key, $args{$key} );
54             }
55             }
56 31         93 return $self;
57             }
58              
59             sub make {
60 27     27 1 44 my $class = shift;
61 27 50       68 unless ($class =~ m/^Exception::Class::TCF::/o) {
62 27         89 my $fclass = 'Exception::Class::TCF::' . $class;
63 27 50       55 $class = $fclass if isException($fclass);
64             }
65 27         139 return $class->new(@_);
66             }
67              
68             sub type {
69 21   33 21 1 172 my $type = ref($_[0]) || $_[0];
70 21         62 $type =~ s/^Exception::Class::TCF:://o;
71 21         58 return $type;
72             }
73              
74             sub setFields {
75 0     0 1 0 my($self) = shift;
76 0 0       0 if (ref $self) {
77 0         0 my ( $key, $val );
78 0         0 while ( ( $key, $val ) = splice @_, 2, 0 ) {
79 0 0       0 next if $self->isProtected($val);
80 0         0 $self->{$key} = $val;
81             }
82             }
83 0         0 return $self;
84             }
85              
86             sub hasField {
87 4 50   4 1 30 ref($_[0]) && exists $_[0]->{$_[1]};
88             }
89              
90 0     0 1 0 sub protectedFields { keys %PROTECTED }
91 0     0 0 0 sub isProtected { exists $PROTECTED{$_[1]} }
92              
93             sub removeFields {
94 0     0 1 0 my($self) = shift;
95 0 0       0 if (ref $self) {
96 0         0 my($name);
97 0         0 foreach ($name) {
98 0 0       0 next if $PROTECTED{$name};
99 0         0 delete $self->{$name};
100             }
101             }
102 0         0 return $self;
103             }
104              
105             sub getField {
106 0 0   0 1 0 ref($_[0]) && $_[0]->{$_[1]};
107             }
108              
109             sub setMessage {
110 1     1 1 10 my ( $self, $msg ) = @_;
111 1 50       5 if (ref $self) {
112 1         2 $self->{'Message'} = $msg;
113             }
114 1         3 return $self;
115             }
116              
117             sub message {
118 5 50 33 5 1 68 ref($_[0]) && exists $_[0]->{'Message'} && $_[0]->{'Message'};
119             }
120              
121             my $dTHROWING;
122             my ( @ARGS, $EXCEPTION, $CATCHING, $THROWING, @STACK );
123             my ( $HANDLE_DIE, $HANDLE_WARN );
124              
125             ### These variables are used for the following purposes:
126             ### $EXCEPTION
127             ### contains the current active exception and
128             ### @ARGS
129             ### the remaining arguments to the throw that threw it.
130             ### $CATCHING
131             ### tells if we're in a handler (but haven't entered any
132             ### try blocks in the handler).
133             ### $THROWING
134             ### tells if we have an active exception.
135             ### $dTHROWING
136             ### is used for shortlived communication between throw and try,
137             ### it is often the same as $THROWING but not always.
138             ### @STACK
139             ### is used for the stack needed to implement the scoping rules
140             ### for the active exception.
141             ### $HANDLE_DIE
142             ### set if an ordinary die should be considered as throwing
143             ### a Die exception
144             ### $HANDLE_WARN
145             ### set if a warn should be considered as throwing
146             ### a Warning exception
147              
148             sub handleDie {
149 1     1 1 54 $HANDLE_DIE = defined $_[0];
150             }
151              
152             sub handleWarn {
153 0     0 1 0 my $oldhw = $HANDLE_WARN;
154 0         0 $HANDLE_WARN = $_[0];
155             }
156              
157             sub deactivate {
158 0 0   0 1 0 if ($THROWING) {
159 0         0 undef $EXCEPTION;
160 0         0 undef @ARGS;
161 0         0 $THROWING = $CATCHING = 0;
162             }
163             }
164              
165             sub dieMess {
166 4     4 0 7 my($self) = @_;
167 4         9 my($type) = $self->type;
168 4         8 my $UNCAUGHT = $DEFAULT_UNCAUGHT;
169 4 50       23 if ($self->hasField('DyingMessage')) {
170 0         0 $UNCAUGHT = $self->getField('DyingMessage');
171             }
172 4         15 my $mess = sprintf $UNCAUGHT, $type;
173 4 50       14 if ( $mess =~ m/\n$/o ) {
174 0         0 return $mess;
175             }
176             else {
177 4         21 my ( $pack, $file, $line ) = caller 2;
178 4 50       22 ( $pack, $file, $line ) = caller 3 if ($pack eq 'Exception::Class::TCF');
179 4         66 return "$mess at $file line $line\n";
180             }
181             }
182              
183             sub die {
184 2     2 1 18 CORE::die $_[0]->dieMess;
185             }
186              
187             sub isThrowing {
188 2 100   2 1 29 $THROWING || $CATCHING;
189             }
190              
191             sub throw (@) {
192 54     54 1 161 my ( $self, @args ) = @_;
193             # throw;
194 54 100       111 unless (@_) {
195 6 50 66     19 unless ( $CATCHING || $THROWING ) {
196 0         0 $THROWING = 0;
197 0         0 my ( $pack, $file, $line ) = caller;
198 0         0 CORE::die "Rethrow without an active exception at $file line $line\n";
199             }
200 6         21 $EXCEPTION->throw(@ARGS); ## To get correct inheritance
201             }
202 48 100       111 $self = make($self) unless ref $self;
203             ### Check here that it is an exception? or in make?
204             # Is in a try block
205 48 100       101 if ( @STACK ) {
206 44         43 $EXCEPTION = $self;
207 44         69 @ARGS = @args;
208 44         203 local $SIG{'__DIE__'} = 'IGNORE';
209 44         56 $THROWING = 1;
210 44         46 $dTHROWING = 1;
211 44         395 CORE::die; ## Maybe $self->die(@args) so Warning does not throw?
212             }
213             # Thrown to the wolves
214             else {
215 4         30 $self->die(@args);
216             }
217             }
218              
219             ## We 'my' some functions to make them unchangeable from the outside
220              
221             my $findException = sub {
222             my($class,$excs) = @_;
223             if ($class eq 'Exception::Class::TCF') {
224             return grep($_ eq 'Default', @$excs) ? 'Default' : "";
225             }
226             my $fclass = $class;
227             $class =~ s/^Exception::Class::TCF:://o;
228             return $class if $class eq 'Die' && !$HANDLE_DIE;
229             foreach (@$excs) {
230             return $_ if &isBelow($fclass,$_);
231             }
232             "";
233             };
234              
235             my $popFrame = sub {
236             ($EXCEPTION,$CATCHING,$THROWING,@ARGS) = @{pop @STACK};
237             };
238              
239             my $pushFrame = sub {
240             push @STACK,[$EXCEPTION,$CATCHING,$THROWING,@ARGS];
241             $CATCHING = $THROWING = 0;
242             undef @ARGS;
243             undef $EXCEPTION;
244             };
245              
246             package Exception::Class::TCF::Warning;
247              
248             package Exception::Class::TCF;
249              
250             sub try (&@) {
251 46     46 1 251 my($block,@catches) = @_;
252 46         45 my($exc,@args,$res);
253 46         77 &$pushFrame;
254             $HANDLE_WARN &&
255 46 50   0   90 local ( $SIG{'__WARN__'} = sub { throw Exception::Class::TCF::Warning @_; } );
  0         0  
256 46         47 $dTHROWING = 0;
257 46         66 $res = eval { &$block() };
  46         104  
258 46         259 $exc = $EXCEPTION;
259 46         126 @args = @ARGS;
260 46 100       116 if ($@) {
261 40         42 my($action,$type,%excs,@excs,$finalAction);
262 40         126 while (($type,$action) = splice @catches,0,2) {
263 47 50       171 unless (ref $action eq 'CODE') {
264 0         0 my($pack,$file,$line) = caller;
265 0         0 warn "Handler for exception key $type is not a function ",
266             "reference at $file line $line\n";
267 0         0 next;
268             }
269            
270 47         52 $type =~ s/^Exception::Class::TCF:://o;
271 47 100       96 $type = 'Exception::Class::TCF' if $type eq 'Default';
272 47 100       82 if ($type eq 'Finally') {
273 1 50       5 $finalAction = $action if ref $action eq 'CODE';
274 1         4 next;
275             }
276 46         81 $excs{$type} = $action;
277 46         146 push @excs,$type;
278             }
279 40         59 my $catchDie = exists $excs{'Die'};
280             # A 'die', not a 'throw'
281 40 100       82 unless ($dTHROWING) {
282 4 100 66     20 if ($catchDie || $HANDLE_DIE) {
283 1         3 $exc = new Exception::Class::TCF::Die;
284 1         3 @args = ($@);
285             }
286             else {
287 3         5 &$popFrame();
288 3         14 CORE::die $@;
289             }
290             }
291 37         40 $dTHROWING = 0;
292 37 50       74 my $class = ref($exc) ? ref($exc) : $exc;
293 37         73 my $raisedType = &$findException($class,\@excs);
294 37 100       117 unless (exists $excs{$raisedType}) {
295 4         7 &$popFrame;
296 4 50       11 &$finalAction() if defined $finalAction;
297 4         21 return $exc->throw(@args);
298             }
299 33         41 $CATCHING = 1;
300 33         46 $res = eval { &{$excs{$raisedType}}($exc,@args) };
  33         35  
  33         104  
301 33         176 $CATCHING = 0;
302 33         40 $exc = $EXCEPTION;
303 33         70 @args = @ARGS;
304 33         72 &$popFrame();
305 33 100       93 &$finalAction() if defined $finalAction;
306 33 100       71 return $exc->throw(@args) if $dTHROWING;
307 26 100       45 CORE::die $@ if $@;
308 25         471 return $res;
309             }
310 6         11 &$popFrame();
311 6         23 my(%catches) = @catches;
312 6 100       19 &{$catches{'Finally'}}() if ref $catches{'Finally'} eq 'CODE';
  2         6  
313 6         24 $res;
314             }
315              
316             sub catch (@) {
317 42     42 1 603 return @_;
318             }
319              
320             sub finally (&) {
321 2     2 1 7 return ('Finally',$_[0]);
322             }
323              
324             package Exception::Class::TCF::Die;
325 1     1   10 use vars '@ISA';
  1         14  
  1         495  
326             @ISA = qw(Exception::Class::TCF::Error);
327              
328             package Exception::Class::TCF::Error;
329 1     1   21 use vars '@ISA';
  1         2  
  1         357  
330             @ISA = qw(Exception::Class::TCF);
331              
332             sub die {
333 2     2   11 die $_[0]->dieMess;
334             }
335              
336             package Exception::Class::TCF::Warning;
337 1     1   7 use vars '@ISA';
  1         3  
  1         193  
338             @ISA = qw(Exception::Class::TCF);
339              
340             sub die {
341 0     0     warn $_[0]->dieMess;
342             }
343              
344             1;
345              
346             __DATA__