File Coverage

blib/lib/Astro/STSDAS/Table/Binary.pm
Criterion Covered Total %
statement 21 215 9.7
branch 0 64 0.0
condition 0 26 0.0
subroutine 7 22 31.8
pod 12 12 100.0
total 40 339 11.8


line stmt bran cond sub pod time code
1             package Astro::STSDAS::Table::Binary;
2              
3             our $VERSION = '0.13';
4              
5 1     1   4 use strict;
  1         2  
  1         30  
6 1     1   5 use warnings;
  1         2  
  1         22  
7 1     1   846 use FileHandle;
  1         11580  
  1         5  
8 1     1   478 use Carp qw( carp croak );
  1         2  
  1         59  
9              
10              
11             our @ISA = qw( Astro::STSDAS::Table::Base );
12              
13 1     1   842 use Astro::STSDAS::Table::Base;
  1         179  
  1         57  
14 1     1   8 use Astro::STSDAS::Table::Constants;
  1         2  
  1         1888  
15              
16              
17             # things read in from the table
18             our @hdr_fields = (
19             'nhp', # number of header parameters
20             'nhp_a', # number of header parameters allocated
21             'nrows', # number of rows written to table
22             'nrows_a', # number of rows allocated
23             'ncols', # number of column descriptors in table
24             'ncols_a', # number of column descriptors allocated
25             'row_used', # size in CHAR_SZ of space used in row
26             'row_len', # size in CHAR_SZ of row length
27             'ttype', # type of table (row or column ordered)
28             'version', # STSDAS software version number
29             );
30              
31              
32              
33              
34             # row_len - the row length, in bytes, for row-ordered tables
35             # row_used - the actual length of the row in the file, in bytes, for
36             # row-ordered tables
37             # row_els - the number of elements in a row (includes vector elements)
38             # nrows_a - the number of rows allocated (in a column ordered table)
39             # ttype - the type of table (either TT_ROW_ORDER or TT_COL_ORDER)
40             # version - "table software version number" from STSDAS created tables
41             # row - the next record (zero based) to be read in
42             # last_col_idx - index of the last column read, for column ordered tables
43             # last_col - the last column read, for column ordered tables
44             # buf - the input buffer, row_len bytes wide.
45             # have_vecs - the table has vectors
46              
47              
48             sub new
49             {
50 0     0 1   my $this = shift;
51 0   0       my $class = ref($this) || $this;
52              
53 0           my $self = $class->SUPER::new();
54              
55 0           $self->{last_col_idx} = -1;
56 0           $self->{last_col} = undef;
57 0           $self->{row} = 0;
58              
59 0           bless $self, $class;
60             }
61              
62              
63             # _read_hdr
64              
65             # _read_hdr is an internal routine which digests the binary table
66             # header. besides stocking the table hash with the information, it
67             # converts lengths into bytes and creates a pack() compatible format
68             # for reading in rows. It also initializes various things.
69              
70             sub _read_hdr
71             {
72 0     0     my $self = shift;
73              
74 0           my $buf;
75 0 0         read( $self->{fh}, $buf, 12 * $TypeSize{TY_INT()} ) ==
76             12 * $TypeSize{TY_INT()} or
77             croak( "no data or error reading header\n");
78              
79 0           my %rawhdr;
80 0           @rawhdr{@hdr_fields} = unpack( 'i10', $buf );
81              
82             # save a few of the values
83 0           $self->{row_len} = $rawhdr{row_len} * CHAR_SZ;
84 0           $self->{row_used} = $rawhdr{row_used} * CHAR_SZ;
85 0           $self->{nrows} = $rawhdr{nrows};
86 0           $self->{nrows_a} = $rawhdr{nrows_a};
87 0           $self->{ttype} = $rawhdr{ttype};
88 0           $self->{version} = $rawhdr{version};
89              
90              
91 0 0         if ( $rawhdr{nhp} )
92             {
93 0           my $pars = $self->{pars};
94              
95 0           for my $i ( 1 .. $rawhdr{nhp} )
96             {
97 0 0         read( $self->{fh}, $buf, 80 ) == 80 or
98             croak( "ran out of data reading header parameter $i\n" );
99              
100 0           my $name = unpack('A*', substr($buf, 0, 8));
101 0           my $type = $HdrType{substr( $buf, 8, 1 )};
102 0           ( my $value = substr( $buf, 9, 71 ) ) =~ s/\0.*//g;
103              
104 0 0         if ( $type eq TY_STRING )
105             {
106 0           $value =~ s/^'|'$//g;
107             }
108              
109 0           $pars->add( $name, $value, undef, $type );
110             }
111             }
112              
113 0 0         if ( $rawhdr{nhp_a} > $rawhdr{nhp} )
114             {
115 0           my $i = 1;
116 0           while ( $i++ <= $rawhdr{nhp_a} - $rawhdr{nhp} )
117             {
118 0 0         read( $self->{fh}, $buf, 80 ) == 80 or
119             croak( "ran out of data reading padding header record $i\n" );
120             }
121             }
122              
123 0           $self->{row_els} = 0;
124 0           for my $coln ( 1 .. $rawhdr{ncols} )
125             {
126 0 0         read( $self->{fh}, $buf, 16 * $TypeSize{TY_INT()} ) ==
127             16 * $TypeSize{TY_INT()} or
128             croak( "ran out of data reading column definition $coln\n" );
129              
130 0           my ( $idx, $offset, $space, $type ) = unpack( 'i4', $buf );
131              
132 0           my $nelem;
133 0           ( my $name = substr( $buf, 4 * $TypeSize{TY_INT()}, 20 ) ) =~ s/\0.*//g;
134 0           ( my $units = substr( $buf, 9 * $TypeSize{TY_INT()}, 20 ) ) =~ s/\0.*//g;
135 0           ( my $format = substr( $buf, 14 * $TypeSize{TY_INT()}, 8 ) ) =~ s/\0.*//g;
136              
137              
138             # if type is negative, it's a string; $type also gives length
139 0 0         if ( $type < 0 )
140             {
141 0           $type = TY_STRING;
142 0           $nelem = -$type;
143             }
144              
145             # nope. get the length from the number of bytes in the
146             # element
147             else
148             {
149 0           $nelem = $space * CHAR_SZ / $TypeSize{$type};
150             }
151              
152 0           my $col = $self->{cols}->add( $name, $units, $format, $idx,
153             $offset * CHAR_SZ,
154             $type, $nelem );
155              
156 0           $self->{row_els} += $nelem;
157              
158 0           $self->{row_extract} .= $col->fmt;
159             }
160              
161 0 0         if ( $rawhdr{ncols_a} > $rawhdr{ncols} )
162             {
163 0           my $nbytes = ($rawhdr{ncols_a} - $rawhdr{ncols}) * 16 * $TypeSize{TY_INT()} ;
164 0 0         read( $self->{fh}, $buf, $nbytes ) == $nbytes
165             or
166             croak( "ran out of data reading padding column definitions\n" );
167             }
168              
169             # reuse buffers for speed (Perl will size them correctly the first time
170             # they're used
171              
172             # input raw buffer
173 0           $self->{buf} = '';
174              
175             # input extracted data buffer (inlined vector elements)
176 0           $self->{data} = [];
177              
178             # input data, vector elements split out
179 0           $self->{row_arr} = [];
180 0           $self->{row_hash} = {};
181              
182 0           $self->{have_vecs} = grep { $_->is_vector } $self->{cols}->cols;
  0            
183             }
184              
185 0     0 1   sub is_row_order { $_[0]->{ttype} == TT_ROW_ORDER }
186 0     0 1   sub is_col_order { $_[0]->{ttype} == TT_COL_ORDER }
187              
188             sub read_rows_hash
189             {
190 0     0 1   my $self = shift;
191              
192             # pre extend
193 0           my @rows;
194 0           $#rows = $self->{nrows} - 1;
195 0           @rows = ();
196              
197 0 0         if ( $self->is_row_order )
198             {
199 0           my $row;
200 0           push @rows, $row while $row = $self->read_row_row_hash( {} ) ;
201             }
202              
203             else
204             {
205 0           1 while $self->read_col_row_hash( \@rows );
206             }
207              
208 0           \@rows;
209             }
210              
211             sub read_rows_array
212             {
213 0     0 1   my $self = shift;
214 0           my %attr = ( VecSplit => 1,
215 0 0 0       ( @_ && 'HASH' eq ref($_[-1]) ? %{pop @_} : () ) );
216              
217 0           my @rows;
218 0           $#rows = $self->{nrows} - 1;
219 0           @rows = ();
220              
221 0 0         if ( $self->is_row_order )
222             {
223 0           my $idx = 0;
224 0           my $row;
225 0           push @rows, $row
226             while ( $row = $self->read_row_row_array( [], \%attr ) );
227             }
228              
229             else
230             {
231             # pre extend row arrays.
232 0           @rows = map {
233 0           my @row;
234 0           $#row = $self->{ncols} - 1;
235 0           @row = ();
236 0           \@row;
237             } ( 0..($self->{nrows}-1) );
238              
239 0           1 while $self->read_col_row_array( \@rows, \%attr );
240             }
241              
242 0           \@rows;
243             }
244              
245             sub read_cols_hash
246             {
247 0     0 1   my $self = shift;
248              
249 0           my $cols_arr = $self->read_cols_array;
250              
251 0           my %cols;
252 0           @cols{ map { lc $_ } $self->{cols}->names } = @{$cols_arr};
  0            
  0            
253              
254 0           \%cols;
255             }
256              
257             sub read_cols_array
258             {
259 0     0 1   my $self = shift;
260              
261 0           my @cols;
262              
263 0 0         if ( $self->is_row_order )
264             {
265 0           @cols = map { my @a; $#a = $self->{nrows} - 1; \@a }
  0            
  0            
  0            
266             1..($self->{cols}->ncols - 1);
267              
268 1     1   1025 use integer;
  1         10  
  1         6  
269 0           my $idx;
270 0           while( my $row = $self->read_row_row_array )
271             {
272 0           $cols[$_][$idx] = $row->[$_] for 0..($self->{cols}->ncols-1);
273 0           $idx++;
274             }
275             }
276              
277             else
278             {
279 0           my $data;
280 0           push @cols, $data
281             while ( $data = $self->read_col_col_array );
282             }
283              
284 0           \@cols;
285             }
286              
287             # read a column from a column oriented table into a row hash
288             sub read_col_row_hash
289             {
290 0     0 1   my $self = shift;
291 0           my $row = shift;
292              
293 0           my $data = $self->read_col_col_array;
294 0           my $name = $self->{last_col}->name;
295              
296 0           eval qq{
297             use integer;
298             \$row->[\$_]{$name} = \$data->[\$_]
299             foreach 0..($self->{nrows}-1) ;
300             };
301              
302 0           1;
303             }
304              
305             # read a column from a column oriented table into a row array
306             sub read_col_row_array
307             {
308 0     0 1   my $self = shift;
309              
310 0           my %attr = ( VecSplit => 1,
311 0 0 0       ( @_ && 'HASH' eq ref($_[-1]) ? %{pop @_} : () ) );
312              
313 0           my $row = shift;
314 0           my $col = $self->{last_col};
315              
316 0           my $data = $self->read_col_col_array( \%attr );
317              
318 0 0 0       if ( !$self->{have_vecs} || $attr{VecSplit} )
319             {
320 0           eval qq{
321             use integer;
322             push \@{\$row->[\$_]}, \$data->[\$_] foreach 0..$self->{nrows}-1 ;
323             };
324             }
325              
326             else
327             {
328             # make special code; don't know yet if this is worth it
329 0           my $dp = 0;
330 0           my $dpd = $col->nelem;
331 0           my $dpd1 = $col->nelem - 1;
332              
333 0           eval qq{
334             use integer;
335 0           for my \$idx ( 0..@{\$self->{nrows}}-1 )
336             {
337             push \@{\$row->[\$idx]}, [ \@{\$data}[\$dp .. (\$dp + $dpd1) ] ] ;
338             \$dp += $dpd
339             }
340             };
341            
342             }
343              
344 0           1;
345             }
346              
347             sub read_col_col_array
348             {
349 0     0 1   my $self = shift;
350 0           my $uattr = shift;
351              
352 0 0         my %attr = { VecSplit => 1, defined $uattr ? %$uattr : () };
353              
354 0           my $data = $self->_read_next_col;
355 0           my $col = $self->{last_col};
356              
357             # if there are no vector elements, just return the data as is
358 0 0 0       return $data if 1 == $col->{nelem} || ! $attr{VecSplit};
359              
360             # deal with the vector elements
361 0           my @vec_data;
362 0           $#vec_data = $self->{nrows} - 1;
363              
364             # make special code; don't know yet if this is worth it
365 0           my $dp = 0;
366 0           my $dpd = $col->nelem;
367 0           my $dpd1 = $col->nelem - 1;
368 0           eval qq{
369             use integer;
370 0           for my \$idx ( 0..@{\$self->{nrows}}-1 )
371             {
372             \$vec_data[\$idx] = [ \@{\$data}[\$dp .. (\$dp + $dpd1) ] ] ;
373             \$dp += $dpd;
374             }
375             };
376 0           return \@vec_data;
377             }
378              
379              
380             sub _read_next_col
381             {
382 0     0     my $self = shift;
383              
384             # if we're all done, don't bother
385 0 0         return () if $self->{last_col_idx} + 1 == $self->{cols}->ncols;
386              
387 0           my $buf;
388              
389 0           my $col = $self->{cols}->byidx($self->{last_col_idx} + 1);
390              
391 0           my $ndata = $self->{nrows_a} * $col->nelem;
392 0           my $nbytes = $ndata * $col->size;
393              
394 0           my $nread = read( $self->{fh}, $buf, $nbytes );
395              
396 0 0         unless( $nbytes == $nread )
397             {
398             # gotta be exactly $nbytes or we're loused
399 0           croak( "incomplete read of column ", $self->{last_col_idx} + 2, "\n" )
400             }
401              
402 0           my @data = unpack( $col->ifmt . $ndata , $buf );
403              
404             # clean the data;
405 0 0         unless ( $col->is_string )
406             {
407 0   0       $col->is_indef($_) && ($_ = undef) foreach @data;
408             }
409              
410 0           $self->{last_col_idx}++;
411 0           $self->{last_col} = $col;
412              
413 0           \@data;
414             }
415              
416              
417             sub read_row_row_array
418             {
419 0     0 1   my $self = shift;
420 0           $self->_read_next_row( @_ );
421             }
422              
423              
424              
425             sub read_row_row_hash
426             {
427 0     0 1   my $self = shift;
428 0   0       my $row = shift || $self->{row_hash};
429              
430 0           my $row_arr = $self->_read_next_row( $row );
431              
432 0 0         return undef unless $row;
433              
434 0           @{$row}{ map { lc $_ } $self->{cols}->names } = @$row_arr;
  0            
  0            
435 0           return $row;
436             }
437              
438             # _read_row
439              
440             # This reads the next row from a row-ordered table into an array, in the
441             # same order as that of the columns in the table. Vector elements are
442             # stored as described above. It returns the undefined value if there are
443             # no more data.
444              
445             sub _read_next_row
446             {
447 0     0     my $self = shift;
448              
449 0           my %attr = ( VecSplit => 1,
450 0 0 0       ( @_ && 'HASH' eq ref($_[-1]) ? %{pop @_} : () ) );
451              
452             # store the row data in what the caller wants, or the object's buffer.
453 0   0       my $row = shift || $self->{row_arr};
454              
455             # guess what? there's (possibly only sometimes) an extra row filled
456             # with indefs at the end of the file! so we can't actually use
457             # the end of file condition to stop reading. ACKCKCKCCKKC!
458 0 0         return undef if $self->{row} == $self->{nrows};
459              
460              
461 0           my $nread = read( $self->{fh}, $self->{buf}, $self->{row_len});
462              
463 0 0         unless( $nread == $self->{row_len} )
464             {
465             # if it's not zero, then we've read too little, and that's a no-no
466 0 0         croak( "incomplete last record (", $self->{row}+1, ")\n" )
467             if 0 != $nread;
468              
469             # EOF
470 0           return undef;
471             }
472              
473             # if we're not splitting vectors up, just read into the final destination
474 0 0         my $data = $attr{VecSplit} ? $self->{data} : $row;
475              
476             # pre-extend. should only hurt once
477 0           $#{$data} = $self->{row_els};
  0            
478 0           @{$data} = unpack( $self->{row_extract}, $self->{buf} );
  0            
479              
480 0 0         if ( $attr{VecSplit} )
481             {
482             # this is slow, but it works. clean it up someday
483              
484             # prextend the row. should only hurt once.
485 0           $#{$row} = $self->{cols}->ncols;
  0            
486 0           @$row = ();
487 0           for my $col ( $self->{cols}->cols )
488             {
489 0 0         if ( $col->nelem == 1 )
490             {
491 0           my $elem = shift @$data;
492 0 0         push @$row, $col->is_indef($elem) ? undef : $elem;
493             }
494             else
495             {
496 0 0         push @$row,
497 0           [ map { $col->is_indef($_) ? undef : $_ }
498             splice( @$data, 0, $col->nelem ) ];
499             }
500             }
501             }
502             else
503             {
504 0           my $idx = 0;
505 0           for my $col ( $self->{cols}->cols )
506             {
507 0           for ( my $nelem = $col->nelem; $nelem ; $nelem--, $idx++ )
508             {
509 0 0         $data->[$idx] = undef if $col->is_indef($data->[$idx]);
510             }
511             }
512             }
513              
514 0           $self->{row}++;
515 0           return $row;
516             }
517              
518              
519             1;
520              
521             __END__