uk.ac.starlink.fits.BintableStarTable Maven / Gradle / Ivy
Show all versions of stil Show documentation
package uk.ac.starlink.fits;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.ac.starlink.table.AbstractStarTable;
import uk.ac.starlink.table.ColumnInfo;
import uk.ac.starlink.table.DefaultValueInfo;
import uk.ac.starlink.table.DescribedValue;
import uk.ac.starlink.table.HealpixTableInfo;
import uk.ac.starlink.table.RowAccess;
import uk.ac.starlink.table.RowSequence;
import uk.ac.starlink.table.TableFormatException;
import uk.ac.starlink.table.TableSink;
import uk.ac.starlink.table.Tables;
import uk.ac.starlink.table.ValueInfo;
/**
* An implementation of the StarTable interface which uses a FITS BINTABLE
* extension.
*
* The implementation varies according to whether random or sequential-only
* access is provided by the underlying data access.
* A factory method is provided to create an appropriate instance.
*
*
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.
*
*
Limited support is provided for the
* HEALPix-FITS convention;
* the relevant {@link uk.ac.starlink.table.HealpixTableInfo} table parameters
* are added, but any BAD_DATA keyword value is ignored,
* and the 1024-element array-valued column variant of the format is not
* understood.
*
* @author Mark Taylor
*/
public abstract class BintableStarTable extends AbstractStarTable {
private final int ncol_;
private final long nrow_;
private final ColumnInfo[] colInfos_;
private final ColumnReader[] colReaders_;
private final long rowLength_;
private final long[] colOffsets_;
/** Column aux metadata key for TNULLn cards. */
public final static ValueInfo TNULL_INFO = new DefaultValueInfo(
Tables.NULL_VALUE_INFO.getName(),
Tables.NULL_VALUE_INFO.getContentClass(),
"Bad value indicator (TNULLn card)" );
/** Column aux metadata key for TSCALn cards. */
public final static ValueInfo TSCAL_INFO = new DefaultValueInfo(
"Scale",
Double.class,
"Multiplier for values (TSCALn card)" );
/** Column aux metadata key for TZEROn cards. */
public final static ValueInfo TZERO_INFO = new DefaultValueInfo(
"Zero",
Number.class,
"Offset for values (TZEROn card)" );
/** Column aux metadata key for TDISPn cards. */
public final static ValueInfo TDISP_INFO = new DefaultValueInfo(
"Format",
String.class,
"Display format in FORTRAN notation (TDISPn card)" );
/** Column aux metadata key for TBCOLn cards. */
public final static ValueInfo TBCOL_INFO = new DefaultValueInfo(
"Start column",
Integer.class,
"Start column for data (TBCOLn card)" );
/** Column aux metadata key for TFORMn cards. */
public final static ValueInfo TFORM_INFO = new DefaultValueInfo(
"Format code",
String.class,
"Data type code (TFORMn card)" );
/** Column aux metadata key for stringified longs offset. */
public final static ValueInfo LONGOFF_INFO = new DefaultValueInfo(
"LONG_OFFSET",
String.class,
"Offset value added when turning 64-bit integer into string; " +
"only present for stringified long values with non-zero offset" );
/** Known aux data infos. */
private static final ValueInfo[] AUX_DATA_INFOS = new ValueInfo[] {
TNULL_INFO, TSCAL_INFO, TZERO_INFO, TDISP_INFO, TBCOL_INFO, TFORM_INFO,
LONGOFF_INFO,
};
/** BigInteger equal to 2^63 (== Long.MAX_VALUE + 1). */
static final BigInteger TWO63 = BigInteger.ONE.shiftLeft( 63 );
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.fits" );
/**
* Constructor.
*
* @param hdr FITS header cards
* @param isRandom true if the data access will be random-access,
* false for sequential-only
* @param wide convention for representing extended columns;
* use null to avoid use of extended columns
*/
protected BintableStarTable( FitsHeader hdr, boolean isRandom,
WideFits wide )
throws IOException {
/* Check we have a BINTABLE header. */
if ( ! hdr.getStringValue( "XTENSION" ).equals( "BINTABLE" ) ) {
throw new TableFormatException( "Not a binary table header" );
}
/* Get Table characteristics. */
nrow_ = hdr.getRequiredLongValue( "NAXIS2" );
int ncolStd = hdr.getRequiredIntValue( "TFIELDS" );
ncol_ = wide != null
? wide.getExtendedColumnCount( hdr, ncolStd )
: ncolStd;
boolean hasExtCol = ncol_ > ncolStd;
if ( hasExtCol ) {
assert wide != null;
AbstractWideFits.logWideRead( logger_, ncolStd, ncol_ );
}
/* Record heap start if available. */
final long heapOffset;
if ( isRandom ) {
Long theap = hdr.getLongValue( "THEAP" );
heapOffset =
theap != null ? theap.longValue()
: nrow_ * hdr.getRequiredLongValue( "NAXIS1" );
}
else {
heapOffset = -1;
}
/* Get column characteristics. */
colInfos_ = new ColumnInfo[ ncol_ ];
colReaders_ = new ColumnReader[ 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 );
List auxdata = cinfo.getAuxData();
colInfos_[ icol ] = cinfo;
/* Name. */
String ttype = hdr.getStringValue( colhead.getKeyName( "TTYPE" ) );
if ( ttype != null ) {
cinfo.setName( ttype );
}
/* Units. */
String tunit = hdr.getStringValue( colhead.getKeyName( "TUNIT" ) );
if ( tunit != null ) {
cinfo.setUnitString( tunit );
}
/* Format string. */
String tdisp = hdr.getStringValue( colhead.getKeyName( "TDISP" ) );
if ( tdisp != null ) {
auxdata.add( new DescribedValue( TDISP_INFO, tdisp ) );
}
/* Blank value. */
final long blank;
final boolean hasBlank;
Long tnull = hdr.getLongValue( colhead.getKeyName( "TNULL" ) );
if ( tnull != null ) {
blank = tnull.longValue();
hasBlank = true;
auxdata.add( new DescribedValue( TNULL_INFO, tnull ) );
}
else {
cinfo.setNullable( false );
blank = 0L;
hasBlank = false;
}
/* Shape. */
int[] dims = null;
String tdim = hdr.getStringValue( colhead.getKeyName( "TDIM" ) );
if ( tdim != null ) {
tdim = tdim.trim();
if ( tdim.charAt( 0 ) == '(' &&
tdim.charAt( tdim.length() - 1 ) == ')' ) {
tdim = tdim.substring( 1, tdim.length() - 1 ).trim();
String[] sdims = tdim.split( "," );
if ( sdims.length > 0 ) {
try {
dims = new int[ sdims.length ];
for ( int i = 0; i < sdims.length; i++ ) {
dims[ i ] =
Integer.parseInt( sdims[ i ].trim() );
}
}
catch ( NumberFormatException e ) {
dims = null;
// can't set shape
}
}
}
}
/* Scaling. */
final double scale;
final Number zero;
Double tScal = hdr.getDoubleValue( colhead.getKeyName( "TSCAL" ) );
if ( tScal != null ) {
scale = tScal.doubleValue();
auxdata.add( new DescribedValue( TSCAL_INFO, tScal ) );
}
else {
scale = 1.0;
}
Number tZero = hdr.getNumberValue( colhead.getKeyName( "TZERO" ) );
if ( tZero != null ) {
/* Careful here. For unsigned long values, the TZERO value
* is 9223372036854775808 == 2^63 == Long.MAX_VALUE+1,
* i.e. not storable in a long (out of range) or a double
* (loses precision). So we need to be prepared to use
* arbitrary precision numbers. Check the javadocs when
* manipulating these, behaviour is sometimes surprising. */
BigDecimal zbig = new BigDecimal( tZero.toString() );
boolean zIsInt =
zbig.compareTo( new BigDecimal( zbig.toBigInteger() ) )
== 0;
boolean zInLongRange =
zbig.compareTo( new BigDecimal(
BigInteger.valueOf( Long.MIN_VALUE ) ) ) >= 0 &&
zbig.compareTo( new BigDecimal(
BigInteger.valueOf( Long.MAX_VALUE ) ) ) <= 0;
Object zval;
if ( zbig.compareTo( new BigDecimal( TWO63 ) ) == 0 ) {
zero = TWO63;
zval = tZero;
}
else if ( zIsInt && zInLongRange ) {
zero = new Long( zbig.longValue() );
zval = zero;
}
else {
zero = new Double( zbig.doubleValue() );
zval = zero;
}
DefaultValueInfo zInfo = new DefaultValueInfo( TZERO_INFO );
zInfo.setContentClass( zval.getClass() );
auxdata.add( new DescribedValue( zInfo, zval ) );
}
else {
zero = Long.valueOf( 0 );
}
/* Format code (recorded but otherwise ignored). */
String tbcol = hdr.getStringValue( colhead.getKeyName( "TBCOL" ) );
if ( tbcol != null ) {
int bcolval = Integer.parseInt( tbcol );
auxdata.add( new DescribedValue( TBCOL_INFO,
new Integer( bcolval ) ) );
}
/* Data type. */
final String tform =
hdr.getRequiredStringValue( colhead.getKeyName( "TFORM" ) );
auxdata.add( new DescribedValue( TFORM_INFO, tform ) );
/* Comment (non-standard). */
String tcomm = hdr.getStringValue( colhead.getKeyName( "TCOMM" ) );
if ( tcomm != null ) {
cinfo.setDescription( tcomm );
}
/* UCD (non-standard). */
String tucd = hdr.getStringValue( colhead.getKeyName( "TUCD" ) );
if ( tucd != null ) {
cinfo.setUCD( tucd );
}
/* Utype (non-standard). */
String tutype = hdr.getStringValue( colhead.getKeyName( "TUTYP" ) );
if ( tutype != null ) {
cinfo.setUtype( tutype );
}
/* Construct a data reader for this column. */
ColumnReader reader;
try {
reader = ColumnReader
.createColumnReader( tform, scale, zero, hasBlank,
blank, dims, ttype, heapOffset );
}
catch ( TableFormatException e ) {
throw new TableFormatException( "Error parsing header line "
+ "TFORM" + jcol + " = " + tform,
e );
}
/* Adjust nullability of strings - they can always be
* null, since an empty string (all spaces) is interpreted
* as null. */
if ( reader.getContentClass().equals( String.class ) ) {
cinfo.setNullable( true );
}
/* Do additional column info configuration as directed
* by the reader. */
cinfo.setContentClass( reader.getContentClass() );
/* Don't try to set the shape for zero-element readers;
* ValueInfo doesn't allow zero values in the dimensions array.
* You could argue this is not correct behaviour,
* but I'd be surprised if it inconveniences anybody. */
if ( reader.getLength() > 0 ) {
cinfo.setShape( reader.getShape() );
}
cinfo.setElementSize( reader.getElementSize() );
if ( reader.isUnsignedByte() ) {
cinfo.setAuxDatum( new DescribedValue( Tables.UBYTE_FLAG_INFO,
Boolean.TRUE ) );
}
BigInteger longOff = reader.getLongOffset();
if ( longOff != null ) {
assert ! BigInteger.ZERO.equals( longOff );
cinfo.setAuxDatum( new DescribedValue( LONGOFF_INFO,
longOff.toString() ) );
}
colReaders_[ icol ] = reader;
}
/* Calculate offsets so we know where to look for each cell. */
long leng = 0;
colOffsets_ = new long[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
colOffsets_[ icol ] = leng;
leng += colReaders_[ icol ].getLength();
}
/* Set the row length in bytes. */
rowLength_ = hdr.getRequiredLongValue( "NAXIS1" );
/* Check it against the sum of column lengths, unless we are
* using extended lengths, in which case they are not guaranteed
* to match. In this case it would be a good idea to validate
* using the extension container column's declared length,
* but it's not very easy to do here. */
if ( ! hasExtCol ) {
if ( rowLength_ != leng ) {
throw new TableFormatException( "Got wrong row length: "
+ rowLength_ + " != " + leng );
}
}
/* Get table name. */
String extname = hdr.getStringValue( "EXTNAME" );
if ( extname != null ) {
String tname = extname;
String extver = hdr.getStringValue( "EXTVER" );
if ( extver != null ) {
tname += "-" + extver;
}
setName( tname );
}
/* Look for headers specific to the HEALPix-FITS encoding.
* Note a MOC is not suitable, since it won't have an NSIDE. */
if ( "HEALPIX".equals( hdr.getStringValue( "PIXTYPE" ) ) &&
! ( hdr.getIntValue( "MOCORDER" ) != null || // MOC v1
hdr.getStringValue( "MOCVERS" ) != null ) ) { // MOC v2
HealpixTableInfo hpxInfo = null;
try {
hpxInfo = extractHealpixInfo( hdr, colInfos_ );
}
catch ( Exception e ) {
logger_.log( Level.WARNING,
"HEALPix header parse failure: " + e.getMessage(),
e );
}
if ( hpxInfo != null ) {
getParameters().addAll( Arrays.asList( hpxInfo.toParams() ) );
}
}
/* Any unused header cards become table parameters. */
List params = getParameters();
for ( DescribedValue dval0 : hdr.getUnusedParams() ) {
DescribedValue dval1 = adjustParameter( dval0 );
if ( dval1 != null ) {
params.add( dval1 );
}
}
}
public long getRowCount() {
return nrow_;
}
public int getColumnCount() {
return ncol_;
}
public ColumnInfo getColumnInfo( int icol ) {
return colInfos_[ icol ];
}
public List getColumnAuxDataInfos() {
return Arrays.asList( AUX_DATA_INFOS );
}
/**
* Reads a cell from a given column from the current position in
* a stream.
*
* @param icol the column index corresponding to the cell to be read
* @param stream a stream containing the byte data, positioned to
* the right place
*/
protected Object readCell( BasicInput stream, int icol )
throws IOException {
return colReaders_[ icol ].readValue( stream );
}
/**
* Reads a whole row of the table from the current position in a stream,
* returning a new Object[] array.
*
* @param stream a stream containing the byte data, positioned to
* the right place
* @return ncol-element array of cells for this row
*/
protected Object[] readRow( BasicInput stream ) throws IOException {
Object[] row = new Object[ ncol_ ];
for ( int icol = 0; icol < ncol_; icol++ ) {
row[ icol ] = colReaders_[ icol ].readValue( stream );
}
return row;
}
/**
* Returns the number of bytes occupied in the data stream by a single
* row of the table. This is equal to the sum of the column offsets array.
*
* @return row length in bytes
*/
protected long getRowLength() {
return rowLength_;
}
/**
* Returns the array of byte offsets from the start of the row at
* which each column starts.
*
* @return ncol-element array of byte offsets
*/
protected long[] getColumnOffsets() {
return colOffsets_;
}
/**
* Returns an instance of this class given a data access instance.
*
* @param hdr FITS header cards
* @param inputFact factory for access to the data part of the
* HDU representing a FITS BINTABLE extension
* @param wide convention for representing extended columns;
* use null to avoid use of extended columns
* @return StarTable instance; it will be random-access according to
* whether the input factory is
*/
public static BintableStarTable createTable( FitsHeader hdr,
InputFactory inputFact,
WideFits wide )
throws IOException {
return inputFact.isRandom()
? new RandomBintableStarTable( hdr, inputFact, wide )
: new SequentialBintableStarTable( hdr, inputFact, wide );
}
/**
* Reads a BINTABLE extension from a stream and writes the result to
* a table sink.
*
* @param hdr FITS header object describing the BINTABLE extension
* @param input input stream positioned at the start of the
* data part of the BINTABLE extension
* @param wide convention for representing extended columns;
* use null to avoid use of extended columns
* @param sink destination for the table
*/
public static void streamStarTable( FitsHeader hdr, BasicInput input,
WideFits wide, TableSink sink )
throws IOException {
InputFactory dummyFact = new InputFactory() {
public boolean isRandom() {
return false;
}
public BasicInput createInput( boolean isSeq ) {
throw new UnsupportedOperationException( "Metadata only" );
}
public void close() {
}
};
BintableStarTable meta =
new SequentialBintableStarTable( hdr, dummyFact, wide );
sink.acceptMetadata( meta );
long nrow = meta.getRowCount();
for ( long i = 0; i < nrow; i++ ) {
Object[] row = meta.readRow( input );
sink.acceptRow( row );
}
sink.endRows();
}
/**
* Investigates headers for cards specific to the HEALPix-FITS convention.
* If it looks like the headers describe a HEALPix map,
* a corresponding HealpixTableInfo object is returned,
* otherwise a RuntimeException with an informative message is thrown.
*
* @param hdr header
* @param infos column metadata
* @return healpix metadata object if the headers look appropriate
* @throws RuntimeException if the headers don't look appropriate
* @see HEALPix-FITS convention
*/
private static HealpixTableInfo extractHealpixInfo( FitsHeader hdr,
ColumnInfo[] infos ) {
/* Get NSIDE/level value. */
Long nSide = hdr.getLongValue( "NSIDE" );
if ( nSide == null ) {
throw new IllegalStateException( "No HEALPix NSIDE header" );
}
long nside = nSide.longValue();
final int level = Long.numberOfTrailingZeros( nside );
if ( 1 << level != nside ) {
throw new IllegalStateException( "Invalid HEALPix header value "
+ "NSIDE=" + nside );
}
/* Get ordering scheme. */
final boolean isNest;
String ordering = hdr.getStringValue( "ORDERING" );
if ( ordering == null ) {
throw new IllegalStateException( "Missing HEALPix header "
+ "ORDERING" );
}
else if ( "NESTED".equals( ordering ) ) {
isNest = true;
}
else if ( "RING".equals( ordering ) ) {
isNest = false;
}
else {
throw new IllegalStateException( "Unrecognised HEALPix header "
+ "ORDERING='" + ordering + "'" );
}
/* Get HEALPix index column, if any. */
final String ipixColName;
String indxschm = hdr.getStringValue( "INDXSCHM" );
if ( indxschm == null ) {
logger_.warning( "Missing nominally required HEALPix header "
+ "INDXSCHM, assuming IMPLICIT" );
ipixColName = null;
}
else if ( "IMPLICIT".equals( indxschm ) ) {
ipixColName = null;
}
else if ( "EXPLICIT".equals( indxschm ) ) {
ipixColName = infos[ 0 ].getName();
}
else {
throw new IllegalStateException( "Unrecognised HEALPix header "
+ "INDXSCHM='" + indxschm + "'" );
}
/* Get coordinate system indicator, if any. */
final HealpixTableInfo.HpxCoordSys csys;
String coordsys = hdr.getStringValue( "COORDSYS" );
if ( coordsys != null ) {
if ( coordsys.trim().length() == 1 ) {
csys = HealpixTableInfo.HpxCoordSys
.fromCharacter( coordsys.charAt( 0 ) );
}
else {
csys = guessHealpixCoordSys( coordsys.trim() );
if ( csys != null ) {
logger_.warning( "Guessing HEALPix header COORDSYS='"
+ coordsys + "' -> " + csys.getWord() );
}
}
if ( csys == null ) {
logger_.warning( "Unknown HEALPix header COORDSYS='"
+ coordsys + "'" );
}
}
else {
csys = null;
}
/* Note that the nominally required BAD_DATA value is currently
* ignored. It is against the spirit of normal FITS bad value
* handling, and would require considerable complication
* and inefficiency to handle correctly. */
/* Return a new metadata object. */
return new HealpixTableInfo( level, isNest, ipixColName, csys );
}
/**
* Makes a best guess at a HEALPix coordinate system given a COORDSYS
* header value. There is a "standard" for this, as per the
* HealpixTableInfo.fromCharacter method, but some providers use
* unambiguous but non-standard values, e.g. "GALACTIC" has been seen
* in Planck Legacy Archive data.
*
* @param coordsysTxt non-null content of FITS COORDSYS header
* @param coordsys value, or null if nothing obvious
*/
private static HealpixTableInfo.HpxCoordSys
guessHealpixCoordSys( String coordsysTxt ) {
String ctxt = coordsysTxt.toUpperCase();
if ( ctxt.startsWith( "GAL" ) ) {
return HealpixTableInfo.HpxCoordSys.GALACTIC;
}
else if ( ctxt.startsWith( "ECL" ) ) {
return HealpixTableInfo.HpxCoordSys.ECLIPTIC;
}
else if ( ctxt.startsWith( "EQU" ) ||
ctxt.startsWith( "CEL" ) ) {
return HealpixTableInfo.HpxCoordSys.CELESTIAL;
}
else {
return null;
}
}
/**
* Hook for modifying a DescribedValue to used as a table parameter.
*
* @param dval0 raw described value from header parsing
* @return described value to use as parameter; may be the same as the
* input, or different, or null if not suitable for use
*/
private static DescribedValue adjustParameter( DescribedValue dval0 ) {
Object value = dval0.getValue();
if ( value instanceof String ) {
Object array = FitsUtil.asNumericArray( (String) value );
if ( array instanceof int[] || array instanceof double[] ) {
ValueInfo info0 = dval0.getInfo();
DefaultValueInfo info1 =
new DefaultValueInfo( info0.getName(), array.getClass(),
info0.getDescription() );
info1.setShape( new int[] { Array.getLength( array ) } );
return new DescribedValue( info1, array );
}
else {
return dval0;
}
}
else {
return dval0;
}
}
/**
* Sequential-only BintableStarTable concrete subclass.
*/
private static class SequentialBintableStarTable extends BintableStarTable {
private final InputFactory inputFact_;
/**
* Constructor.
*
* @param hdr FITS header object describing the BINTABLE extension
* @param inputFact creates input streams positioned at the start
* of thedata part of the BINTABLE extension
* @param wide convention for representing extended columns;
* use null to avoid use of extended columns
*/
public SequentialBintableStarTable( FitsHeader hdr,
InputFactory inputFact,
WideFits wide )
throws IOException {
super( hdr, false, wide );
inputFact_ = inputFact;
}
public boolean isRandom() {
return false;
}
public Object getCell( long lrow, int icol ) throws IOException {
throw new UnsupportedOperationException();
}
public Object[] getRow( long lrow ) throws IOException {
throw new UnsupportedOperationException();
}
public RowSequence getRowSequence() throws IOException {
final BasicInput input = inputFact_.createInput( true );
final Object[] beforeStart = new Object[ 0 ];
final long nrow = getRowCount();
final long rowLength = getRowLength();
return new RowSequence() {
long lrow_ = -1;
Object[] row_ = beforeStart;
long nskip_ = 0;
public boolean next() throws IOException {
if ( lrow_ < nrow - 1 ) {
if ( row_ == null ) {
nskip_ += rowLength;
}
row_ = null;
lrow_++;
return true;
}
else {
return false;
}
}
public Object getCell( int icol ) throws IOException {
return getRow()[ icol ];
}
public Object[] getRow() throws IOException {
if ( row_ == beforeStart ) {
throw new IllegalStateException();
}
if ( row_ == null ) {
if ( nskip_ != 0 ) {
input.skip( nskip_ );
nskip_ = 0;
}
row_ = readRow( input );
}
return row_;
}
public void close() throws IOException {
if ( nskip_ != 0 ) {
input.skip( nskip_ );
nskip_ = 0;
}
input.close();
}
};
}
public void close() throws IOException {
inputFact_.close();
}
}
/**
* Random-access BintableStarTable concrete subclass.
*/
private static class RandomBintableStarTable extends BintableStarTable {
private final InputFactory inputFact_;
private final BasicInputThreadLocal randomInputThreadLocal_;
private final long rowLength_;
private final long[] colOffsets_;
/**
* Constructor.
*
* @param hdr FITS header object describing the BINTABLE extension
* @param inputFact creates input streams positioned at the start
* of thedata part of the BINTABLE extension;
* must be random-access
* @param wide convention for representing extended columns;
* use null to avoid use of extended columns
*/
RandomBintableStarTable( FitsHeader hdr, InputFactory inputFact,
WideFits wide )
throws IOException {
super( hdr, true, wide );
inputFact_ = inputFact;
if ( ! inputFact.isRandom() ) {
throw new IllegalArgumentException( "not random" );
}
rowLength_ = getRowLength();
colOffsets_ = getColumnOffsets();
randomInputThreadLocal_ =
new BasicInputThreadLocal( inputFact, false );
}
public boolean isRandom() {
return true;
}
public Object getCell( long lrow, int icol ) throws IOException {
BasicInput randomInput = randomInputThreadLocal_.get();
randomInput.seek( lrow * rowLength_ + colOffsets_[ icol ] );
return readCell( randomInput, icol );
}
public Object[] getRow( long lrow ) throws IOException {
BasicInput randomInput = randomInputThreadLocal_.get();
randomInput.seek( lrow * rowLength_ );
return readRow( randomInput );
}
public RowSequence getRowSequence() throws IOException {
final BasicInput input = inputFact_.createInput( true );
assert input.isRandom();
final long endPos = getRowCount() * rowLength_;
return new RowSequence() {
long pos = -rowLength_;
public boolean next() {
pos += rowLength_;
return pos < endPos;
}
public Object getCell( int icol ) throws IOException {
if ( pos >= 0 && pos < endPos ) {
input.seek( pos + colOffsets_[ icol ] );
return readCell( input, icol );
}
else {
throw new IllegalStateException();
}
}
public Object[] getRow() throws IOException {
if ( pos >= 0 && pos < endPos ) {
input.seek( pos );
return readRow( input );
}
else {
throw new IllegalStateException();
}
}
public void close() throws IOException {
input.close();
}
};
}
public RowAccess getRowAccess() throws IOException {
final BasicInput input = inputFact_.createInput( false );
assert input.isRandom();
return new RowAccess() {
long irow_ = -1;
public void setRowIndex( long irow ) {
irow_ = irow;
}
public Object getCell( int icol ) throws IOException {
input.seek( irow_ * rowLength_ + colOffsets_[ icol ] );
return readCell( input, icol );
}
public Object[] getRow() throws IOException {
input.seek( irow_ * rowLength_ );
return readRow( input );
}
public void close() throws IOException {
input.close();
}
};
}
public void close() throws IOException {
randomInputThreadLocal_.close();
inputFact_.close();
}
}
}