File Coverage

blib/lib/Iterator/BreakOn/Base.pm
Criterion Covered Total %
statement 24 28 85.7
branch n/a
condition n/a
subroutine 8 9 88.8
pod n/a
total 32 37 86.4


line stmt bran cond sub pod time code
1             package Iterator::BreakOn::Base;
2 2     2   14 use strict;
  2         5  
  2         130  
3 2     2   11 use warnings;
  2         9  
  2         67  
4 2     2   11 use Carp;
  2         3  
  2         186  
5 2     2   2324 use utf8;
  2         27  
  2         17  
6 2     2   1971 use English '-no_match_vars';
  2         13427  
  2         12  
7              
8 2     2   2883 use List::MoreUtils qw(uniq first_index);
  2         3408  
  2         209  
9              
10 2     2   1323 use Iterator::BreakOn::X;
  2         8  
  2         69  
11 2     2   1156 use Iterator::BreakOn::Event;
  2         7  
  2         16  
12              
13             # Source: $Id$
14             # Author: $Author$
15             # Date: $Date$
16              
17             our $VERSION = '0.3';
18              
19             my %_defaults = (
20             datasource => undef,
21             getmethod => 'get', # method name for read single values
22             _check_get_method => 0, # internal switch
23             eod => 0, # end of data switch
24             equeue => [], # event queue for dispatch
25             rec_current => undef, # current item
26             rec_next => undef, # next item (for internal use only)
27             break_before => [], # field list for break_before events
28             # (ordered)
29             break_after => [], # field list for break_after events
30             # (ordered)
31             fields => [],
32             code => {}, # event's code
33             private => undef, # reference a private data
34             );
35              
36             #
37             # Public methods
38             #
39              
40             sub new {
41 0     0     my $class = shift;
42 0           my $self = { %_defaults };
43              
44 0           bless $self, $class;
45              
46 0           return $self->init(@_);
47             }
48              
49             sub init {
50             my $self = shift;
51             my %values = @_;
52              
53             ## get the datasource parameter
54             if (not defined($self->{datasource} = $values{datasource})) {
55             Impresor::BreakOn::X::missing->throw( parameter => 'datasource' );
56             }
57              
58             ## get the method name
59             if (defined($values{getmethod})) {
60             $self->{getmethod} = $values{getmethod};
61             }
62              
63             ## get the break before change
64             if (defined($values{break_before})) {
65             $self->_read_breaks_array( 'before', @{$values{break_before}});
66             }
67              
68             ## get the break after change
69             if (defined($values{break_after})) {
70             $self->_read_breaks_array( 'after', @{$values{break_after}});
71             }
72              
73             ## get a list of fields
74             $self->{fields} = [ uniq( @{$self->{break_before}},
75             @{$self->{break_after}}) ];
76              
77             ## on the first, last and every item
78             foreach my $field qw(on_first on_last on_every) {
79             if (defined $values{$field}) {
80             $self->{code}->{$field} = $values{$field};
81             }
82             }
83              
84             ## save the private data if exists
85             if (defined $values{private}) {
86             $self->{private} = $values{private};
87             }
88              
89             return $self;
90             }
91              
92             sub reset {
93             my $self = shift;
94              
95             # clean the event queue
96             $self->{equeue} = [];
97              
98             # clean the value copies
99             $self->{rec_current} = undef;
100             $self->{rec_next} = undef;
101              
102             return $self;
103             }
104              
105             sub run {
106             my $self = shift;
107              
108             ## reset the iterator
109             $self->reset();
110              
111             return $self->_next_event( 'NONE' );
112             }
113              
114             sub next {
115             my $self = shift;
116              
117             if ($self->_next_event( 'on_every')) {
118             return $self->{rec_current};
119             }
120             else {
121             return;
122             }
123             }
124              
125             sub next_event {
126             my $self = shift;
127              
128             return $self->_next_event( 'ALL' );
129             }
130              
131             sub item {
132             my $self = shift;
133              
134             return $self->{rec_current};
135             }
136              
137             sub current_values {
138             my $self = shift;
139             my %values = ();
140              
141             if ($self->{rec_current} and $self->{rec_current}->can('getall')) {
142             %values = $self->{rec_current}->getall();
143             }
144              
145             return wantarray ? %values : \%values;
146             }
147              
148             sub private {
149             my $self = shift;
150              
151             return $self->{private};
152             }
153              
154             #
155             # Private methods
156             #
157              
158             sub _next_event {
159             my $self = shift;
160             my $stop_on = shift || 'NONE';
161              
162             ITEMS:
163             ## read the next item
164             while (1) {
165             EVENTS:
166             ## read the event queue
167             while (my $event = $self->_shift()) {
168             ## if we must stop on all events or this event is the stop
169             ## return the event without process it
170             if ($stop_on eq 'ALL' or $stop_on eq $event->name()) {
171             return $event;
172             }
173             else {
174             ## process the event and get the next
175             $self->_process_event( $event );
176             }
177             }
178              
179             ## checking the state
180             if ($self->{eod}) {
181             return;
182             }
183              
184             ## read the next item
185             if (not $self->_read_next_item()) {
186             ## empty events queue and empty records: end of data
187             return;
188             }
189             }
190              
191             return;
192             }
193              
194             sub _process_event {
195             my $self = shift;
196             my $event = shift;
197             my $name = $event->name();
198              
199             ## return if we don't have code for the event
200             if (not $self->{code}->{$name}) {
201             return;
202             }
203              
204             ## switch on event type
205             if ($name =~ m{on_first|on_last|on_every}xms) {
206             ## call to dispatch code without parameters
207             $self->{code}->{$name}->( $self );
208             }
209             elsif ($name =~ m{^(before|after)_}xms) {
210             ## call to dispatch code with field name and value
211             $self->{code}->{$name}->( $self, $event->field(), $event->value() );
212             }
213              
214             return;
215             }
216              
217             sub _read_next_item {
218             my $self = shift;
219              
220             ## try read the next item ...
221             $self->{rec_next} = $self->_load_item( );
222              
223             #
224             # Special cases
225             #
226              
227             ## is the first item ?
228             if (not $self->{rec_current}) {
229             ## is a empty list ?
230             if (not $self->{rec_next}) {
231             ## yes, only the first and last events
232             $self->_first_events()->_last_events();
233             }
234             else {
235             # move the next item to the current, push the initial and
236             # the break_before events, and the on_every
237             $self->_next_to_current()->
238             _first_events()->
239             _push_all_breaks( 'before' )->
240             _push_on_every();
241             }
242             }
243             ### is the last item ?
244             elsif (not $self->{rec_next}) {
245             ## end of data: break_after and last events
246             $self->_push_all_breaks( 'after' )->_last_events();
247             }
248             else {
249             ## build the break_after events
250             $self->_cmp_fields( 'after', $self->{break_after});
251              
252             ## build the break_before events
253             $self->_cmp_fields( 'before', $self->{break_before});
254              
255             ## every record event
256             $self->_next_to_current()->_push_on_every();
257             }
258              
259             return $self;
260             }
261              
262             sub _next_to_current {
263             my $self = shift;
264              
265             $self->{rec_current} = $self->{rec_next};
266              
267             return $self;
268             }
269              
270             sub _cmp_fields {
271             my $self = shift;
272             my $when = shift; # after | before
273             my $fields_ref = shift; # fields names
274             my @events = ();
275              
276             ## loop around the fields list
277             my $raise_event = 0;
278             my $get = $self->{getmethod};
279             foreach my $field_name (@{ $fields_ref }) {
280             my $current = $self->{rec_current}->$get($field_name);
281             my $next = $self->{rec_next}->$get($field_name);
282              
283             ## if the values are differents (as strings)
284             if ($raise_event or "${current}" ne "${next}") {
285             ## add the event to the list
286             push(@events, $self->_build_break_event( $when, $field_name ));
287             $raise_event = 1;
288             }
289             }
290              
291             ## add the events if not empty
292             if (@events) {
293             if ($when eq 'after') {
294             @events = reverse @events;
295             }
296             $self->_push( @events );
297             }
298              
299             return $self;
300             }
301              
302             sub _load_item {
303             my $self = shift;
304              
305             ## retrieve the next item in the datasource
306             my $item = eval {
307             $self->{datasource}->next();
308             };
309              
310             ## checking fatal errors
311             if ($EVAL_ERROR) {
312             Iterator::BreakOn::X::datasource->throw();
313             }
314              
315             # checking ever the new item and only once the user supplied get method
316             if (defined $item) {
317             if (not $self->{_check_get_method}) {
318             if (not $item->can( $self->{getmethod} )) {
319             Iterator::BreakOn::X::getmethod->throw(
320             get_method => $self->{getmethod}
321             );
322             }
323             $self->{_check_get_method} = 1;
324             }
325             }
326              
327             return $item;
328             }
329              
330             =begin comments
331              
332             This private method add events to the object internal queue. Receive a list of
333             events and each event is a hash reference with the following attributes:
334              
335             =over
336              
337             =item name
338              
339             =item field
340              
341             =item value
342              
343             =back
344              
345             Return the object reference for use in chained calls.
346              
347             =end comments
348              
349             =cut
350              
351             sub _push {
352             my $self = shift;
353              
354             ## loop around the events list
355             foreach my $event (@_) {
356             my $event_object = Iterator::BreakOn::Event->new( $event );
357              
358             ## add to the list of events
359             push(@{ $self->{equeue} }, $event_object );
360             }
361              
362             return $self;
363             }
364              
365             sub _shift {
366             my $self = shift;
367              
368             if (@{ $self->{equeue} }) {
369             my $event = shift @{ $self->{equeue} };
370              
371             return $event;
372             }
373             else {
374             return undef;
375             }
376             }
377              
378             sub _push_on_every {
379             my $self = shift;
380              
381             return $self->_push( { name => 'on_every' } );
382             }
383              
384             sub _push_all_breaks {
385             my $self = shift;
386             my $when = shift; # after or before
387              
388             return $self->_push( $self->_build_all_breaks( $when ) );
389             }
390              
391             sub _build_all_breaks {
392             my $self = shift;
393             my $when = shift; # after or before
394             my @breaks = ();
395              
396             # on every field name for the break
397             foreach my $field_name (@{ $self->{"break_${when}"} }) {
398             # push the event
399             push( @breaks, $self->_build_break_event( $when, $field_name ) );
400             }
401            
402             return $when eq 'after' ? reverse @breaks : @breaks;
403             }
404              
405             sub _build_break_event {
406             my $self = shift;
407             my $when = shift; # after or before
408             my $field = shift; # field name
409             my $value = $self->_get_field_value( $when, $field );
410              
411             return { name => "${when}_${field}",
412             field => $field,
413             value => $value };
414             }
415              
416             sub _get_field_value {
417             my $self = shift;
418             my $when = shift;
419             my $field = shift;
420              
421             my $from = $when eq 'after' ? 'rec_current' : 'rec_next';
422             my $value = $self->{$from} ? $self->{$from}->get($field) : undef;
423              
424             return $value;
425             }
426              
427             sub _first_events {
428             my $self = shift;
429              
430             ## push the event for the first item
431             return $self->_push( { 'name' => 'on_first' } );
432             }
433              
434             sub _last_events {
435             my $self = shift;
436              
437             ## push the event for the last item
438             $self->_push( { name => 'on_last' } );
439              
440             ## and set the state
441             $self->{eod} = 1;
442              
443             return $self;
444             }
445              
446             sub _read_breaks_array {
447             my $self = shift;
448             my $when = shift;
449             my @breaks = @_;
450              
451             BREAKS:
452             while (@breaks) {
453             # take the field name and a hipotetical code reference from the next
454             # item
455             my $field = shift @breaks;
456             my $code = ref($breaks[0]) eq 'CODE' ? shift @breaks : undef;
457              
458             # save the order in the break fields
459             push(@{ $self->{ "break_${when}" } }, $field);
460              
461             # save the code for that event
462             my $event = "${when}_${field}";
463              
464             # using a default closure if the value is not defined
465             if (not defined($code)) {
466             $code = sub {
467             return $event;
468             };
469             }
470              
471             # in a hash table
472             $self->{code}->{ $event } = $code;
473             }
474              
475             return $self;
476             }
477              
478             1;
479             __END__