package hep.io.sio;
import hep.io.xdr.XDRBufferedRandomAccessFile;
import hep.io.xdr.XDRDataInput;
import hep.io.xdr.XDRInputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.zip.InflaterInputStream;
/**
* A class for reading SIO records. Has limited support for random access
* to records within a file.
*/
public class SIOReader implements Closeable {
private XDRDataInput xdr;
private SIORecordImpl currentRecord;
private long nextRecordPosition;
private static int bufferSize = Integer.getInteger("hep.io.sio.BufferSize", 32768).intValue();
/**
* Creates an SIOReader which reads from an input stream. Random access will not be
* supported.
* @param in The stream to read from.
* @throws IOException If an errors occurs.
*/
public SIOReader(InputStream in) throws IOException {
XDRInputStream sio = new XDRInputStream(in);
xdr = sio;
currentRecord = new SIORecordImpl(sio);
}
/**
* Creates an SIOReader which reads from a file. Random access will be supported.
* @param file The file to read from.
* @throws IOException If an error occurs.
* @since 2.1
*/
public SIOReader(File file) throws IOException {
XDRBufferedRandomAccessFile raf = new XDRBufferedRandomAccessFile(file, true, bufferSize);
xdr = raf;
currentRecord = new SIORecordImpl(raf);
}
/**
* Create an SIOReader which reads from a file. Random access will be supported.
* @param file The file to read from.
* @throws IOException If an error occurs.
* @since 2.1
*/
public SIOReader(String file) throws IOException {
XDRBufferedRandomAccessFile raf = new XDRBufferedRandomAccessFile(file, true, bufferSize);
xdr = raf;
currentRecord = new SIORecordImpl(raf);
}
/**
* Test if this reader supports random access.
* @return <code>true</code> if random access is supported.
* @since 2.1
*/
public boolean isRandomAccess() {
return xdr instanceof RandomAccessFile;
}
/**
* Read the next record from this file
* @return The record that was read.
* @throws IOException If an IO exception occurs
*/
public SIORecord readRecord() throws IOException {
nextRecordPosition = currentRecord.nextRecord();
return currentRecord;
}
/**
* Read a record from a given position in a file.
* @param position The position of the record to read
* @return The record that was read
* @throws IOException If an IO exception occurs, or if the file does not support random access
* @since 2.1
*/
public SIORecord readRecord(long position) throws IOException {
seek(position);
return readRecord();
}
/**
* Position the file to read the next record from the given position
* @param position The position of the next record to read. If negative reads from end-of-file+position
* @throws IOException If an IO exception occurs, or if the file does not support random access
* @since 2.1
*/
public void seek(long position) throws IOException {
RandomAccessFile raf = checkRandomAccess();
if (position>=0) {
raf.seek(position);
} else {
raf.seek(raf.length()+position);
}
currentRecord.clear();
nextRecordPosition = position;
}
/**
* Returns the position at which the next record will be read
* @return The position at which the next record will be read
* @throws IOException If an IO Exception occurs
* @since 2.1
*/
public long getNextRecordPosition() throws IOException {
return nextRecordPosition;
}
public void close() throws IOException {
xdr.close();
}
private RandomAccessFile checkRandomAccess() throws IOException {
if (!isRandomAccess()) {
throw new IOException("File does not support random access");
}
return (RandomAccessFile) xdr;
}
private static class SIORecordImpl implements SIORecord {
private SIOBlockImpl currentBlock;
private String name;
private int headerLength;
private int compressedLength;
private int uncompressedLength;
private boolean blocksRead = true;
private boolean compressed;
private long startPos;
private XDRDataInput xdr;
SIORecordImpl(XDRDataInput xdr) throws IOException {
this.xdr = xdr;
}
private void clear() {
blocksRead = false;
compressedLength = 0;
currentBlock = null;
}
long nextRecord() throws IOException {
skipRemainderOfRecord();
readRecordHeader();
startPos = getPosition();
return startPos + pad(compressedLength);
}
private int pad(int size) {
int r = size % 4;
if (r == 0) {
return size;
}
return size + 4 - r;
}
public String getRecordName() {
return name;
}
public int getRecordLength() {
return uncompressedLength;
}
/**
* Get the next block
* @return the next block, or null if there are no more blocks in the record
*/
public SIOBlock getBlock() throws IOException {
if (currentBlock == null) {
if (!compressed) {
if (xdr instanceof XDRInputStream) {
((XDRInputStream) xdr).clearReadLimit();
}
currentBlock = new SIOBlockImpl(new SIOInputStream(getInputStream(compressedLength)), compressedLength);
} else {
currentBlock = new SIOBlockImpl(new SIOInputStream(new InflaterInputStream(getInputStream(compressedLength))), uncompressedLength);
}
blocksRead = true;
}
try {
currentBlock.nextBlock();
return currentBlock;
} catch (EOFException x) {
return null; // no more blocks
}
}
private InputStream getInputStream(int readLimit) {
if (xdr instanceof RandomAccessFile) {
return new RandomAccessFileInputStream((RandomAccessFile) xdr, readLimit);
} else {
((XDRInputStream) xdr).setReadLimit(readLimit);
return (XDRInputStream) xdr;
}
}
private long getPosition() throws IOException {
if (xdr instanceof RandomAccessFile) {
return ((RandomAccessFile) xdr).getFilePointer();
} else {
return ((XDRInputStream) xdr).getBytesRead();
}
}
private void readRecordHeader() throws IOException {
long headerStart = getPosition();
headerLength = xdr.readInt();
int frame = xdr.readInt();
if (frame != 0xabadcafe) {
throw new IOException("Framing error");
}
int control = xdr.readInt();
if ((control & 0xfffe) != 0) {
throw new IOException("Bad control word");
}
compressed = (control & 1) != 0;
compressedLength = xdr.readInt();
uncompressedLength = xdr.readInt();
int l = xdr.readInt();
if (l > headerLength - getPosition() + headerStart) {
throw new IOException("Record name is insane");
}
name = xdr.readString(l);
blocksRead = false;
currentBlock = null;
}
private void skipRemainderOfRecord() throws IOException {
// Start by skipping whatever is left of the previous record
if (xdr instanceof XDRInputStream) {
((XDRInputStream) xdr).clearReadLimit();
}
if (!blocksRead) {
xdr.skipBytes(compressedLength);
} else {
int left = compressedLength - (int) (getPosition() - startPos);
if (left < 0) {
throw new IOException("Record overrun error");
} else {
xdr.skipBytes(left);
}
}
xdr.pad();
}
}
private static class SIOBlockImpl implements SIOBlock {
private String name;
private int recordLength;
private int length;
private int version;
private SIOInputStream xdr;
private long startPos;
private long recordStartPos;
private static final EOFException eof = new EOFException();
SIOBlockImpl(SIOInputStream xdr, int recordLength) throws IOException {
this.xdr = xdr;
this.recordLength = recordLength;
length = 0;
recordStartPos = startPos = xdr.getBytesRead();
}
void nextBlock() throws IOException {
// skip any data remaining from the previous block
xdr.clearReadLimit();
int bytesLeft = length - (int) (xdr.getBytesRead() - startPos);
if (bytesLeft < 0) {
throw new IOException("Block overrun error (block " + name + ")");
} else if (bytesLeft > 0) {
xdr.skipBytes(bytesLeft);
}
// Check if there are more blocks
xdr.pad();
startPos = xdr.getBytesRead();
if (startPos - recordStartPos >= recordLength) {
throw eof;
}
length = xdr.readInt();
xdr.setReadLimit(length - 4);
int frame = xdr.readInt();
if (frame != 0xdeadbeef) {
throw new IOException("Block framing error");
}
version = xdr.readInt();
int l = xdr.readInt();
if (l > length - xdr.getBytesRead() + startPos) {
throw new IOException("Block name is insane");
}
name = xdr.readString(l);
}
public String getBlockName() {
return name;
}
public int getBlockLength() {
return length;
}
public int getBytesLeft() {
return length - (int) (xdr.getBytesRead() - startPos);
}
public int getVersion() {
return version;
}
public int getMajorVersion() {
return (version & 0xffff0000) >> 16;
}
public int getMinorVersion() {
return version & 0xffff;
}
public SIOInputStream getData() {
return xdr;
}
}
private static class RandomAccessFileInputStream extends InputStream {
private RandomAccessFile file;
private int readLimit;
public RandomAccessFileInputStream(RandomAccessFile file, int readLimit) {
this.file = file;
this.readLimit = readLimit;
}
public int read() throws IOException {
if (available() == 0) return -1;
int c = file.read();
readLimit--;
return c;
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
if (available() == 0) throw new EOFException();
int n = file.read(b, off, Math.min(len, available()));
readLimit -= n;
return n;
}
public long skip(long n) throws IOException {
long pos = file.getFilePointer();
long nActual = Math.min(n, available());
file.seek(pos + nActual);
readLimit -= nActual;
return nActual;
}
public int available() throws IOException {
return (int) Math.min(readLimit, file.length() - file.getFilePointer());
}
@Override
public void close() throws IOException {
file.close();
}
}
}