uk.ac.starlink.fits.InputFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stil Show documentation
Show all versions of stil Show documentation
Starlink Tables Infrastructure Library
package uk.ac.starlink.fits;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.logging.Logger;
import uk.ac.starlink.table.ByteStore;
import uk.ac.starlink.table.StoragePolicy;
import uk.ac.starlink.util.Compression;
import uk.ac.starlink.util.DataBufferedInputStream;
import uk.ac.starlink.util.DataSource;
import uk.ac.starlink.util.FileDataSource;
import uk.ac.starlink.util.IOUtils;
import uk.ac.starlink.util.Loader;
/**
* Represents a sequence of bytes, and can create BasicInput objects
* to read it.
*
* @author Mark Taylor
* @since 2 Dec 2014
*/
public abstract class InputFactory implements Closeable {
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.fits" );
/**
* Indicates whether the BasicInput objects created by this factory
* will support random access.
*
* @return true iff BasicInput.isRandom() will return true for
* created objects
*/
public abstract boolean isRandom();
/**
* Returns a BasicInput instance to read this object's byte stream.
*
* @param isSeq if true, the returned object is expected to be used
* for sequential access only; this value is a hint
* which may or may not be used by the implementation
* @return new reader; if isSeq is false and isRandom returns true,
* it will be capable of random access
*/
public abstract BasicInput createInput( boolean isSeq ) throws IOException;
/**
* Constructs an instance of this class to read a given data source.
*
* @param datsrc data source
* @param offset offset into file of stream start
* @param leng number of bytes in stream
* @return new instance
*/
public static InputFactory createFactory( DataSource datsrc,
long offset, long leng )
throws IOException {
boolean isFile = datsrc instanceof FileDataSource;
if ( isFile && datsrc.getCompression() == Compression.NONE ) {
File uncompressedFile = ((FileDataSource) datsrc).getFile();
return createFileFactory( uncompressedFile, offset, leng );
}
else {
if ( isFile ) {
logger_.warning( "Can't map compressed file " + datsrc.getName()
+ " - uncompressing may improve performance" );
}
else {
logger_.info( "Will read stream (not random-access): "
+ datsrc );
}
return createSequentialFactory( datsrc, offset, leng );
}
}
/**
* Constructs an instance of this class to read a given data source
* viewed as a stream, not a file.
*
* @param datsrc data source
* @param offset offset into file of stream start
* @param leng number of bytes in stream
* @return new instance
*/
public static InputFactory createSequentialFactory( final DataSource datsrc,
final long offset,
final long leng ) {
return new AbstractInputFactory( false ) {
public BasicInput createInput( boolean isSeq )
throws IOException {
InputStream in = datsrc.getInputStream();
IOUtils.skip( in, offset );
return createSequentialInput( in );
}
public void close() {
datsrc.close();
}
};
}
/**
* Constructs an instance of this class to read a given uncompressed file.
* It must be uncompressed for the random access to be able to locate
* the right part to read.
*
* @param uncompressedFile unencoded file
* @param offset offset into file of stream start
* @param leng number of bytes in stream
* @return new instance
*/
public static InputFactory createFileFactory( File uncompressedFile,
final long offset,
final long leng )
throws IOException {
final File file = uncompressedFile;
final String logName = file.getName();
Unmapper unmapper = Unmapper.getInstance();
if ( leng <= BlockManager.DEFAULT_BLOCKSIZE * 2 ) {
logger_.info( "Will map as single block: " + logName );
final int ileng = (int) leng;
RandomAccessFile raf = new RandomAccessFile( file, "r" );
final FileChannel chan = raf.getChannel();
final BufferManager bufManager =
new BufferManager( chan, offset, ileng, logName, unmapper );
return new AbstractInputFactory( true ) {
public BasicInput createInput( boolean isSeq )
throws IOException {
return new SimpleMappedInput( bufManager );
}
public void close() throws IOException {
chan.close();
bufManager.close();
}
};
}
else if ( Loader.is64Bit() ) {
RandomAccessFile raf = new RandomAccessFile( file, "r" );
final FileChannel chan = raf.getChannel();
final BlockManager blockManager =
new BlockManager( chan, offset, leng, logName, unmapper,
BlockManager.DEFAULT_BLOCKSIZE );
logger_.info( "Will map as multiple blocks ("
+ blockManager.getBlockCount() + " * "
+ blockManager.getBlockSize() + "): " + logName );
return new AbstractInputFactory( true ) {
public BasicInput createInput( boolean isSeq )
throws IOException {
return BlockMappedInput.createInput( blockManager, !isSeq );
}
public void close() throws IOException {
chan.close();
blockManager.close();
}
};
}
else {
logger_.info( "Will read as BufferedRandomInput: " + file
+ " (avoid too much mapping on 32-bit OS" );
return new AbstractInputFactory( true ) {
public BasicInput createInput( boolean isSeq )
throws IOException {
RandomAccessFile raf = new RandomAccessFile( file, "r" );
return new BufferedRandomInput( raf, offset );
}
public void close() {
}
};
}
}
/**
* Returns an InputFactory for which random access is guaranteed.
* If necessary, the bytes from the data source will be cached
* using the supplied storage policy.
*
* @param datsrc data source
* @param offset offset into file of stream start
* @param leng number of bytes in stream
* @param policy storage policy for caching if required,
* or null for default
* @return new instance
*/
public static InputFactory createRandomFactory( DataSource datsrc,
long offset, long leng,
StoragePolicy policy )
throws IOException {
boolean isFile = datsrc instanceof FileDataSource;
if ( isFile && datsrc.getCompression() == Compression.NONE ) {
File uncompressedFile = ((FileDataSource) datsrc).getFile();
return createFileFactory( uncompressedFile, offset, leng );
}
else {
logger_.warning( "Caching non-random FITS data" );
ByteStore byteStore =
( policy == null ? StoragePolicy.getDefaultPolicy() : policy )
.makeByteStore();
try (
InputStream in = datsrc.getInputStream();
OutputStream out = byteStore.getOutputStream();
) {
IOUtils.skip( in, offset );
byte[] buf = new byte[ 1024 * 64 ];
while ( leng > 0 ) {
int nb = in.read( buf, 0,
(int) Math.min( leng, buf.length ) );
if ( nb < 0 ) {
throw new EOFException( "FITS file too short" );
}
out.write( buf, 0, nb );
leng -= nb;
}
}
return createByteStoreFactory( byteStore );
}
}
/**
* Returns an input factory based on a populated bytestore.
*
* @param byteStore cache containing byte content
* @return new input factory
*/
public static InputFactory
createByteStoreFactory( final ByteStore byteStore )
throws IOException {
final ByteBuffer[] bbufs = byteStore.toByteBuffers();
final int nbuf = bbufs.length;
if ( nbuf == 1 ) {
final ByteBuffer bbuf = bbufs[ 0 ];
return new AbstractInputFactory( true ) {
public BasicInput createInput( boolean isSeq ) {
return new ByteBufferInput( bbuf.duplicate() );
}
public void close() throws IOException {
byteStore.close();
}
};
}
else {
return new AbstractInputFactory( true ) {
public BasicInput createInput( boolean isSeq ) {
ByteBuffer[] bbufs1 = new ByteBuffer[ nbuf ];
for ( int ib = 0; ib < nbuf; ib++ ) {
bbufs1[ ib ] = bbufs[ ib ].duplicate();
}
return new MultiByteBufferInput( bbufs1 );
}
public void close() throws IOException {
byteStore.close();
}
};
}
}
/**
* Returns a non-random-access BasicInput based on a supplied input stream.
*
* @param in input stream
* @return non-random BasicInput
*/
public static BasicInput createSequentialInput( final InputStream in ) {
final DataBufferedInputStream dataIn =
new DataBufferedInputStream( in );
return new BasicInput() {
public byte readByte() throws IOException {
return dataIn.readByte();
}
public short readShort() throws IOException {
return dataIn.readShort();
}
public int readInt() throws IOException {
return dataIn.readInt();
}
public long readLong() throws IOException {
return dataIn.readLong();
}
public float readFloat() throws IOException {
return dataIn.readFloat();
}
public double readDouble() throws IOException {
return dataIn.readDouble();
}
public void readBytes( byte[] bbuf ) throws IOException {
dataIn.readFully( bbuf );
}
public void skip( long nbyte ) throws IOException {
IOUtils.skip( dataIn, nbyte );
}
public boolean isRandom() {
return false;
}
public void seek( long offset ) {
throw new UnsupportedOperationException();
}
public long getOffset() {
throw new UnsupportedOperationException();
}
public void close() {
}
};
}
/**
* Utility partial implementation.
*/
private static abstract class AbstractInputFactory extends InputFactory {
private final boolean isRandom_;
/**
* Constructor.
*
* @param isRandom true iff random access is supported
*/
AbstractInputFactory( boolean isRandom ) {
isRandom_ = isRandom;
}
public boolean isRandom() {
return isRandom_;
}
}
}