uk.ac.starlink.fits.ColFitsStarTable Maven / Gradle / Ivy
package uk.ac.starlink.fits;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.logging.Logger;
import uk.ac.starlink.table.AbstractStarTable;
import uk.ac.starlink.table.ColumnInfo;
import uk.ac.starlink.table.RowAccess;
import uk.ac.starlink.table.RowSequence;
import uk.ac.starlink.table.TableFormatException;
import uk.ac.starlink.table.Tables;
import uk.ac.starlink.util.Compression;
import uk.ac.starlink.util.DataSource;
import uk.ac.starlink.util.FileDataSource;
import uk.ac.starlink.util.Loader;
/**
* StarTable based on a single-row FITS BINTABLE which contains the
* data for an entire column in each cell of the table.
* The BINTABLE must be the first extension of a FITS file.
*
* Some instances of this class hang on to file descriptors.
* If you are in danger of running out of that resource before
* insstances are garbage collected, you can call the {@link #close}
* method to release them. Attempting to read data following
* such a call may result in an exception.
*
* @author Mark Taylor
* @since 21 Jun 2006
*/
public class ColFitsStarTable extends AbstractStarTable {
private final int ncol_;
private final long nrow_;
private final ValueReader[] valReaders_;
private final InputFactory[] inputFacts_;
private final boolean isRandom_;
private final ColumnReader[] randomColReaders_;
private final Closeable closer_;
private final static Logger logger_ =
Logger.getLogger( "uk.ac.starlink.fits" );
/**
* Constructor.
*
* @param datsrc data source containing the FITS data
* @param hdr header of the HDU containing the table
* @param dataPos offset into file
of the start of the
* data part of the HDU
* @param force true to make a table if we possibly can,
* false to reject if it doesn't look very much like one
* @param wide convention for representing extended columns;
* use null to avoid use of extended columns
*/
public ColFitsStarTable( DataSource datsrc, FitsHeader hdr, long dataPos,
boolean force, WideFits wide )
throws IOException {
/* Check it's a BINTABLE. */
if ( ! "BINTABLE".equals( hdr.getStringValue( "XTENSION" ) ) ) {
throw new TableFormatException( "HDU 1 not BINTABLE" );
}
/* Check it has exactly one row. */
if ( hdr.getRequiredIntValue( "NAXIS2" ) != 1 ) {
throw new TableFormatException( "Doesn't have exactly one row" );
}
/* Find the number of columns. */
int ncolStd = hdr.getRequiredIntValue( "TFIELDS" );
ncol_ = wide == null
? ncolStd
: wide.getExtendedColumnCount( hdr, ncolStd );
boolean hasExtCol = ncol_ > ncolStd;
if ( hasExtCol ) {
assert wide != null;
AbstractWideFits.logWideRead( logger_, ncolStd, ncol_ );
}
/* Nasty hack.
* LDAC is a somewhat eccentric FITS variant that has a single-cell
* BINTABLE as HDU1 containing an array of FITS header cards.
* That looks like colfits, and causes the later HDU to be
* ignored in format auto-detection mode, since colfits is not
* a multi-table-capable format. So reject this HDU explicitly here,
* in auto-detection format it will get passed on to some other
* format handler (basic FITS).
* See http://marvinweb.astro.uni-bonn.de/
* data_products/THELIWWW/LDAC/LDAC_concepts.html */
if ( ! force &&
"LDAC_IMHEAD".equals( hdr.getStringValue( "EXTNAME" ) ) ) {
throw new TableFormatException( "Reject LDAC_IMHEAD table" );
}
/* Read metadata for each column from the FITS header. */
long nrow = 0;
valReaders_ = new ValueReader[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
int jcol = icol + 1;
BintableColumnHeader colhead =
hasExtCol && jcol >= ncolStd
? wide.createExtendedHeader( ncolStd, jcol )
: BintableColumnHeader.createStandardHeader( jcol );
ColumnInfo cinfo = new ColumnInfo( "col" + jcol );
/* Format character and length. */
String tform =
hdr.getRequiredStringValue( colhead.getKeyName( "TFORM" ) )
.trim();
char formatChar = tform.charAt( tform.length() - 1 );
/* Use a special value if we have byte values offset by 128
* (which allows one to represent signed bytes as unsigned ones). */
if ( formatChar == 'B' ) {
Number tzero =
hdr.getNumberValue( colhead.getKeyName( "TZERO" ) );
Number tscale =
hdr.getNumberValue( colhead.getKeyName( "TSCALE" ) );
if ( ( tzero != null && tzero.doubleValue() == -128.0 ) &&
( tscale == null || tscale.doubleValue() == 1.0 ) ) {
formatChar = 'b';
}
}
long nitem;
try {
nitem =
Long.parseLong( tform.substring( 0, tform.length() - 1 ) );
}
catch ( NumberFormatException e ) {
throw new TableFormatException( "Bad "
+ colhead.getKeyName( "TFORM" )
+ " '" + tform + '"' );
}
/* Row count and item shape. */
String tdims = hdr.getStringValue( colhead.getKeyName( "TDIM" ) );
long[] dims = parseTdim( tdims );
if ( dims == null ) {
logger_.info( "No " + colhead.getKeyName( "TDIM" )
+ "; assume (1," + nitem + ")" );
dims = new long[] { 1, nitem };
}
if ( multiply( dims ) != nitem ) {
throw new TableFormatException( colhead.getKeyName( "TDIM" )
+ " doesn't match "
+ colhead.getKeyName( "TFORM" ) );
}
int[] itemShape = new int[ dims.length - 1 ];
for ( int i = 0; i < dims.length - 1; i++ ) {
itemShape[ i ] = Tables.checkedLongToInt( dims[ i ] );
}
long nr = dims[ dims.length - 1 ];
if ( icol == 0 ) {
nrow = nr;
}
else {
if ( nr != nrow ) {
throw new TableFormatException( "Row count mismatch" );
}
}
/* Null value. */
Long blank = hdr.getLongValue( colhead.getKeyName( "TNULL" ) );
/* Informational metadata. */
String ttype = hdr.getStringValue( colhead.getKeyName( "TTYPE" ) );
if ( ttype != null ) {
cinfo.setName( ttype );
}
String tunit = hdr.getStringValue( colhead.getKeyName( "TUNIT" ) );
if ( tunit != null ) {
cinfo.setUnitString( tunit );
}
String tcomm = hdr.getStringValue( colhead.getKeyName( "TCOMM" ) );
if ( tcomm != null ) {
cinfo.setDescription( tcomm );
}
String tucd = hdr.getStringValue( colhead.getKeyName( "TUCD" ) );
if ( tucd != null ) {
cinfo.setUCD( tucd );
}
String tutype = hdr.getStringValue( colhead.getKeyName( "TUTYP" ) );
if ( tutype != null ) {
cinfo.setUtype( tutype );
}
/* Create a column reader for the current column. */
valReaders_[ icol ] =
createValueReader( formatChar, cinfo, itemShape, blank );
}
nrow_ = nrow;
/* Prepare an InputFactory for the region of the input file
* corresponding to each column. */
inputFacts_ = new InputFactory[ ncol_ ];
/* Use random access if possible. */
boolean isFile = datsrc instanceof FileDataSource;
if ( isFile &&
datsrc.getCompression() == Compression.NONE &&
( Loader.is64Bit() ||
((FileDataSource) datsrc).getFile().length() < 1 << 29 ) ) {
isRandom_ = true;
/* Use a single file channel for all columns, though each will
* have its own InputFactory. */
File file = ((FileDataSource) datsrc).getFile();
RandomAccessFile raf = new RandomAccessFile( file, "r" );
final FileChannel chan = raf.getChannel();
closer_ = new Closeable() {
public void close() throws IOException {
chan.close();
}
};
long pos = dataPos;
for ( int icol = 0; icol < ncol_; icol++ ) {
final long offset = pos;
final long leng = valReaders_[ icol ].getItemBytes() * nrow_;
pos += leng;
String logName =
file.getName() + ":col" + ( icol + 1 ) + "/" + ncol_;
inputFacts_[ icol ] =
createInputFactory( chan, offset, leng, logName );
}
}
/* Otherwise use a different stream for each column. */
else {
if ( isFile && datsrc.getCompression() != Compression.NONE ) {
logger_.warning( "Can't map compressed file " + datsrc.getName()
+ " - uncompressing may improve performance" );
}
isRandom_ = false;
long pos = dataPos;
for ( int icol = 0; icol < ncol_; icol++ ) {
final long offset = pos;
final long leng = valReaders_[ icol ].getItemBytes() * nrow_;
pos += leng;
inputFacts_[ icol ] =
InputFactory
.createSequentialFactory( datsrc, offset, leng );
}
closer_ = new Closeable() {
public void close() {
}
};
}
/* Get table name. */
String tname = hdr.getStringValue( "EXTNAME" );
if ( tname != null ) {
String extver = hdr.getStringValue( "EXTVER" );
if ( extver != null ) {
tname += "-" + extver;
}
setName( tname );
}
/* Add table params containing header card information. */
getParameters().addAll( Arrays.asList( hdr.getUnusedParams() ) );
/* Prepare readers for random access. */
if ( isRandom_ ) {
randomColReaders_ = new ColumnReader[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
final BasicInputThreadLocal bitl =
new BasicInputThreadLocal( inputFacts_[ icol ], false );
randomColReaders_[ icol ] =
new ColumnReader( valReaders_[ icol ] ) {
protected BasicInput getInput() {
return bitl.get();
}
public void close() throws IOException {
bitl.close();
}
};
}
}
else {
randomColReaders_ = null;
}
}
public int getColumnCount() {
return ncol_;
}
public long getRowCount() {
return nrow_;
}
public boolean isRandom() {
return isRandom_;
}
public ColumnInfo getColumnInfo( int icol ) {
return valReaders_[ icol ].getColumnInfo();
}
public Object getCell( long irow, int icol ) throws IOException {
if ( randomColReaders_ != null ) {
return randomColReaders_[ icol ].readIndexedCell( irow );
}
else {
throw new UnsupportedOperationException();
}
}
public Object[] getRow( long irow ) throws IOException {
if ( randomColReaders_ != null ) {
Object[] row = new Object[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
row[ icol ] = randomColReaders_[ icol ].readIndexedCell( irow );
}
return row;
}
else {
throw new UnsupportedOperationException();
}
}
public RowSequence getRowSequence() throws IOException {
return new ColFitsRowSequence();
}
public RowAccess getRowAccess() throws IOException {
if ( isRandom_ ) {
return new ColFitsRowAccess();
}
else {
throw new UnsupportedOperationException();
}
}
public void close() throws IOException {
closer_.close();
}
/**
* Parse the content of a FITS TDIMnn header card.
* This has the form (a,b,c,..), where a, b, c are integer values.
* Returns null if tdim
is not of the expected form.
*
* @param tdim header card value
* @return array of values
*/
static long[] parseTdim( String tdim ) {
if ( tdim == null ) {
return null;
}
tdim = tdim.trim();
if ( tdim.charAt( 0 ) != '(' ||
tdim.charAt( tdim.length() - 1 ) != ')' ) {
return null;
}
String[] sdims = tdim.substring( 1, tdim.length() - 1 ).split( "," );
long[] dims = new long[ sdims.length ];
for ( int i = 0; i < sdims.length; i++ ) {
try {
dims[ i ] = Long.parseLong( sdims[ i ].trim() );
}
catch ( NumberFormatException e ) {
return null;
}
}
return dims;
}
/**
* Utility method to multiply elements of a long[]
array.
*
* @param dims array
* @return product of elements of dims
*/
private static long multiply( long[] dims ) {
long product = 1;
for ( int i = 0; i < dims.length; i++ ) {
product *= dims[ i ];
}
return product;
}
/**
* Utility method to multiply elements of an int[]
array.
*
* @param dims array
* @return product of elements of dims
*/
private static long multiply( int[] dims ) {
long product = 1;
for ( int i = 0; i < dims.length; i++ ) {
product *= dims[ i ];
}
return product;
}
/**
* Recasts a long
value which is known to be in range
* to an int
.
*
* @param lval long value, must be between Integer.MIN_VALUE
* and Integer.MAX_VALUE
* @return int
equivalent of lval
* @param throws AssertionError if lval
is out of range
* (and asssertions are enabled)
*/
private static int toInt( long lval ) {
int ival = (int) lval;
assert (long) ival == lval;
return ival;
}
/**
* Factory method to create ValueReader instances.
*
* @param formatChar FITS format character indicating data type
* @param info column metadata object
* @param itemShape dimensions of each cell in the column
* @param blank null value for column, if any
*/
private static ValueReader createValueReader( char formatChar,
ColumnInfo info,
int[] itemShape,
Number blank )
throws IOException {
final int itemSize = Tables.checkedLongToInt( multiply( itemShape ) );
final int[] SCALAR = new int[ 0 ];
/* Scalar column types. */
if ( itemSize == 1 ) {
if ( formatChar == 'L' ) {
info.setContentClass( Boolean.class );
return new ValueReader( info, 1, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
switch ( in.readByte() ) {
case 'T':
return Boolean.TRUE;
case 'F':
return Boolean.FALSE;
default:
return null;
}
}
};
}
else if ( formatChar == 'A' ) {
info.setContentClass( Character.class );
info.setNullable( false );
return new ValueReader( info, 1, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
char chr = (char) ( in.readByte() & 0xff );
return chr == '\0'
? null
: Character.valueOf( chr );
}
};
}
else if ( formatChar == 'B' ) {
info.setContentClass( Short.class );
final boolean hasBad = blank != null;
final byte badval = hasBad ? blank.byteValue() : (byte) 0;
info.setNullable( hasBad );
return new ValueReader( info, 1, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
byte val = in.readByte();
return ( hasBad && val == badval )
? null
: Short.valueOf( (short) ( val & 0xff ) );
}
};
}
else if ( formatChar == 'b' ) {
info.setContentClass( Short.class );
final boolean hasBad = blank != null;
final byte badval = hasBad ? blank.byteValue() : (byte) 0;
info.setNullable( hasBad );
return new ValueReader( info, 1, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
byte val = in.readByte();
return ( hasBad && val == badval )
? null
: Short.valueOf( (short) val );
}
};
}
else if ( formatChar == 'I' ) {
info.setContentClass( Short.class );
final boolean hasBad = blank != null;
final short badval = hasBad ? blank.shortValue() : (short) 0;
info.setNullable( hasBad );
return new ValueReader( info, 2, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
short val = in.readShort();
return ( hasBad && val == badval )
? null
: Short.valueOf( val );
}
};
}
else if ( formatChar == 'J' ) {
info.setContentClass( Integer.class );
final boolean hasBad = blank != null;
final int badval = hasBad ? blank.intValue() : 0;
info.setNullable( hasBad );
return new ValueReader( info, 4, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
int val = in.readInt();
return ( hasBad && val == badval )
? null
: Integer.valueOf( val );
}
};
}
else if ( formatChar == 'K' ) {
info.setContentClass( Long.class );
final boolean hasBad = blank != null;
final long badval = hasBad ? blank.longValue() : 0L;
info.setNullable( hasBad );
return new ValueReader( info, 8, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
long val = in.readLong();
return ( hasBad && val == badval )
? null
: Long.valueOf( val );
}
};
}
else if ( formatChar == 'E' ) {
info.setContentClass( Float.class );
return new ValueReader( info, 4, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
return new Float( in.readFloat() );
}
};
}
else if ( formatChar == 'D' ) {
info.setContentClass( Double.class );
return new ValueReader( info, 8, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
return new Double( in.readDouble() );
}
};
}
}
/* Array column types. */
else {
info.setShape( itemShape );
info.setNullable( false );
if ( formatChar == 'L' ) {
info.setContentClass( boolean[].class );
return new ValueReader( info, 1, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
boolean[] val = new boolean[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = in.readByte() == (byte) 'T';
}
return val;
}
};
}
else if ( formatChar == 'B' ) {
info.setContentClass( short[].class );
return new ValueReader( info, 1, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
short[] val = new short[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = (short) ( in.readByte() & 0xff );
}
return val;
}
};
}
else if ( formatChar == 'b' ) {
info.setContentClass( short[].class );
return new ValueReader( info, 1, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
short[] val = new short[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = (short) in.readByte();
}
return val;
}
};
}
else if ( formatChar == 'I' ) {
info.setContentClass( short[].class );
return new ValueReader( info, 2, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
short[] val = new short[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = in.readShort();
}
return val;
}
};
}
else if ( formatChar == 'J' ) {
info.setContentClass( int[].class );
return new ValueReader( info, 4, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
int[] val = new int[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = in.readInt();
}
return val;
}
};
}
else if ( formatChar == 'K' ) {
info.setContentClass( long[].class );
return new ValueReader( info, 8, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
long[] val = new long[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = in.readLong();
}
return val;
}
};
}
else if ( formatChar == 'E' ) {
info.setContentClass( float[].class );
return new ValueReader( info, 4, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
float[] val = new float[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = in.readFloat();
}
return val;
}
};
}
else if ( formatChar == 'D' ) {
info.setContentClass( double[].class );
return new ValueReader( info, 8, itemShape ) {
Object readValue( BasicInput in ) throws IOException {
double[] val = new double[ itemSize ];
for ( int i = 0; i < itemSize; i++ ) {
val[ i ] = in.readDouble();
}
return val;
}
};
}
else if ( formatChar == 'A' ) {
final int sleng = itemShape[ 0 ];
info.setElementSize( sleng );
info.setNullable( true );
if ( itemShape.length == 1 ) {
info.setContentClass( String.class );
return new ValueReader( info, sleng, SCALAR ) {
Object readValue( BasicInput in ) throws IOException {
final char[] charBuf = new char[ sleng ];
int iend = 0;
boolean end = false;
for ( int i = 0; i < sleng; i++ ) {
byte b = in.readByte();
if ( b == 0 ) {
end = true;
}
if ( ! end ) {
charBuf[ i ] = (char) ( b & 0xff );
if ( b != (byte) ' ' ) {
iend = i + 1;
}
}
}
return iend > 0 ? new String( charBuf, 0, iend )
: null;
}
};
}
else {
info.setContentClass( String[].class );
int[] sshape = new int[ itemShape.length - 1 ];
System.arraycopy( itemShape, 1, sshape, 0, sshape.length );
info.setShape( sshape );
final int nstring = itemSize / sleng;
assert nstring * sleng == itemSize;
return new ValueReader( info, sleng, sshape ) {
Object readValue( BasicInput in ) throws IOException {
String[] val = new String[ nstring ];
char[] charBuf = new char[ sleng ];
for ( int is = 0; is < nstring; is++ ) {
int iend = 0;
boolean end = false;
for ( int ic = 0; ic < sleng; ic++ ) {
byte b = in.readByte();
if ( b == 0 ) {
end = true;
}
if ( ! end ) {
charBuf[ ic ] = (char) ( b & 0xff );
if ( b != (byte) ' ' ) {
iend = ic + 1;
}
}
}
val[ is ] = iend > 0
? new String( charBuf, 0, iend )
: null;
}
return val;
}
};
}
}
}
/* Unknown. */
throw new IOException( "Unknown TFORM character '" + formatChar + "'" );
}
/**
* Returns an input factory suitable for accessing column data.
* Closing the factory will not close the channel.
*
* @param chan file channel
* @param offset offset into file of column data start
* @param leng size of column data in bytes
* @param logName column description suitable for logging messages
* @return input factory
*/
private static InputFactory createInputFactory( FileChannel chan,
long offset, long leng,
String logName ) {
Unmapper unmapper = Unmapper.getInstance();
if ( leng <= BlockManager.DEFAULT_BLOCKSIZE ) {
final BufferManager bufManager =
new BufferManager( chan, offset, (int) leng, logName, unmapper);
return new InputFactory() {
public boolean isRandom() {
return true;
}
public BasicInput createInput( boolean isSeq )
throws IOException {
return new SimpleMappedInput( bufManager );
}
public void close() {
bufManager.close();
}
};
}
else {
final BlockManager blockManager =
new BlockManager( chan, offset, leng, logName, unmapper,
BlockManager.DEFAULT_BLOCKSIZE );
return new InputFactory() {
public boolean isRandom() {
return true;
}
public BasicInput createInput( boolean isSeq )
throws IOException {
return BlockMappedInput.createInput( blockManager, !isSeq );
}
public void close() {
blockManager.close();
}
};
}
}
/**
* RowSequence implementation for this table.
*/
private class ColFitsRowSequence implements RowSequence {
private final ColumnReader[] seqColReaders_;
private final long[] cursors_;
private final Object[] lastValues_;
private long irow_;
/**
* Constructor.
*/
ColFitsRowSequence() throws IOException {
seqColReaders_ = new ColumnReader[ ncol_ ];
cursors_ = new long[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
final InputFactory inputFact = inputFacts_[ icol ];
seqColReaders_[ icol ] =
new ColumnReader( valReaders_[ icol ] ) {
BasicInput input_;
protected BasicInput getInput() throws IOException {
if ( input_ == null ) {
input_ = inputFact.createInput( true );
}
return input_;
}
public void close() throws IOException {
if ( input_ != null ) {
input_.close();
}
}
};
cursors_[ icol ] = -1;
}
lastValues_ = new Object[ ncol_ ];
irow_ = -1;
}
public boolean next() {
return ++irow_ < nrow_;
}
public Object getCell( int icol ) throws IOException {
ColumnReader colReader = seqColReaders_[ icol ];
long nskip = irow_ - cursors_[ icol ];
if ( nskip > 0 ) {
if ( nskip > 1 ) {
colReader.skipCells( nskip - 1 );
}
lastValues_[ icol ] = colReader.readNextCell();
cursors_[ icol ] = irow_;
}
else if ( irow_ < 0 ) {
throw new IllegalStateException();
}
return lastValues_[ icol ];
}
public Object[] getRow() throws IOException {
Object[] row = new Object[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
row[ icol ] = getCell( icol );
}
return row;
}
public void close() throws IOException {
for ( ColumnReader colReader : seqColReaders_ ) {
colReader.close();
}
}
}
/**
* RowAccess implementation for this table.
*/
private class ColFitsRowAccess implements RowAccess {
private final ColumnReader[] colReaders_;
private final Object[] row_;
private long irow_;
ColFitsRowAccess() throws IOException {
colReaders_ = new ColumnReader[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
final InputFactory inFact = inputFacts_[ icol ];
colReaders_[ icol ] = new ColumnReader( valReaders_[ icol ] ) {
BasicInput input_;
protected BasicInput getInput() throws IOException {
if ( input_ == null ) {
input_ = inFact.createInput( false );
}
return input_;
}
public void close() throws IOException {
if ( input_ != null ) {
input_.close();
}
}
};
}
row_ = new Object[ ncol_ ];
}
public void setRowIndex( long irow ) {
irow_ = irow;
}
public Object getCell( int icol ) throws IOException {
return colReaders_[ icol ].readIndexedCell( irow_ );
}
public Object[] getRow() throws IOException {
for ( int icol = 0; icol < ncol_; icol++ ) {
row_[ icol ] = colReaders_[ icol ].readIndexedCell( irow_ );
}
return row_;
}
public void close() throws IOException {
for ( ColumnReader colReader : colReaders_ ) {
colReader.close();
}
}
}
/**
* Knows how to read data items of a particular type from a byte store.
* Instances of this class are thread-safe.
*/
private static abstract class ValueReader {
private final ColumnInfo info_;
private final int typeBytes_;
private final int[] itemShape_;
private final int itemBytes_;
/**
* Constructor.
*
* @param info column metadata
* @param typeBytes number of bytes per scalar element
* @param itemShape dimensions of column cells
*/
ValueReader( ColumnInfo info, int typeBytes, int[] itemShape ) {
info_ = info;
typeBytes_ = typeBytes;
itemShape_ = itemShape;
itemBytes_ = Tables.checkedLongToInt( multiply( itemShape ) )
* typeBytes;
}
/**
* Reads an object from a byte buffer.
*
* @param in input stream, positioned at read point
* @throws IOException in case of a read error
*/
abstract Object readValue( BasicInput in ) throws IOException;
/**
* Returns the number of bytes for a single cell of this type.
*
* @return bytes per cell
*/
public int getItemBytes() {
return itemBytes_;
}
/**
* Returns the column metadata associated with this reader.
*
* @return column metadata
*/
public ColumnInfo getColumnInfo() {
return info_;
}
}
/**
* Aggregates a ValueReader and a BasicInput to read cell values for
* a given column.
*/
private static abstract class ColumnReader implements Closeable {
private final ValueReader valReader_;
private final long itemBytes_;
/**
* Constructor.
*
* @param valueReader understands data format
*/
ColumnReader( ValueReader valReader ) {
valReader_ = valReader;
itemBytes_ = valReader.getItemBytes();
}
/**
* Returns the object that provides byte data.
*
* @return basic input
*/
protected abstract BasicInput getInput() throws IOException;
/**
* Positions ready to read the value for a given row.
*
* @param irow row index
*/
Object readIndexedCell( long irow ) throws IOException {
BasicInput input = getInput();
input.seek( irow * itemBytes_ );
return valReader_.readValue( input );
}
/**
* Reads the next cell value.
*
* @return cell value
*/
Object readNextCell() throws IOException {
return valReader_.readValue( getInput() );
}
void skipCells( long nrow ) throws IOException {
getInput().skip( itemBytes_ * nrow );
}
}
}