package org.sfc.io.block;
import java.io.IOException;
import java.io.InputStream;
import org.sfc.io.ReferenceCountedInputStream;
/**
* A block data input stream degroups blocks read from a source into stream
* data that can be read sequentially.
*
* @author Thomas Weise
*/
public class BlockInputStream extends ReferenceCountedInputStream
{
/**
* The source input stream.
*/
private final InputStream m_in ;
/**
* The input buffer.
*/
private final byte[] m_buffer ;
/**
* The current position in the output data.
*/
private int m_pos ;
/**
* The count of bytes available.
*/
private int m_available ;
/**
* The next byte.
*/
private int m_next;
/**
* 0 = first read, 1 = open, 2 = closed
*/
private byte m_state ;
/**
* The block handler interface to delegate to.
*/
private final IBlockHandler m_handler ;
/**
* Create a new block data input stream.
* @param p_in The input stream to read from.
* @param p_block_size The block size to use for reading.
* @param p_handler The block handler interface to delegate to.
*/
public BlockInputStream (final InputStream p_in,
final int p_block_size,
final IBlockHandler p_handler)
{
super();
this.m_buffer = new byte[(p_block_size > 0) ? p_block_size : 1];
this.m_handler = p_handler;
this.m_in = p_in;
}
/**
* Fill the internal buffer.
* @param p_b The buffer to fill.
* @return <code>true</code> if data became available, <code>false</code>
* otherwise.
* @throws IOException if something fails.
*/
private final boolean fill_buffer (final byte[] p_b) throws IOException
{
int l_l, l_b;
byte l_s;
if((l_s = this.m_state) > 1) return false;
this.m_pos = 0;
l_l = p_b.length;
l_b = this.m_next;
if(l_s > 0)
{
p_b[0] = ((byte)l_b);
l_b = (this.m_in.read(p_b, 1, l_l-1)+1);
}
else
{
l_b = this.m_in.read(p_b, 0, l_l);
this.m_state = 1;
}
if(l_b < l_l)
{
this.m_available = 0;
return false;
}
this.m_handler.on_block(p_b);
l_b = this.m_in.read();
if(l_b < 0)
{
for(--l_l; l_l >= 0; l_l--)
{
if(p_b[l_l] == BlockOutputStream.END_MARK) break;
}
this.m_available = l_l;
this.m_state = 2;
return (l_l > 0);
}
this.m_available = l_l;
this.m_next = l_b;
return true;
}
/**
* Reads up to <code>len</code> bytes of data from the input stream into
* an array of bytes. An attempt is made to read as many as
* <code>len</code> bytes, but a smaller number may be read.
* The number of bytes actually read is returned as an integer.
*
* <p> This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
*
* <p> If <code>b</code> is <code>null</code>, a
* <code>NullPointerException</code> is thrown.
*
* <p> If <code>off</code> is negative, or <code>len</code> is negative, or
* <code>off+len</code> is greater than the length of the array
* <code>b</code>, then an <code>IndexOutOfBoundsException</code> is
* thrown.
*
* <p> If <code>len</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at end of
* file, the value <code>-1</code> is returned; otherwise, at least one
* byte is read and stored into <code>b</code>.
*
* <p> The first byte read is stored into element <code>b[off]</code>, the
* next one into <code>b[off+1]</code>, and so on. The number of bytes read
* is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
* bytes actually read; these bytes will be stored in elements
* <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[off+</code><i>k</i><code>]</code> through
* <code>b[off+len-1]</code> unaffected.
*
* <p> In every case, elements <code>b[0]</code> through
* <code>b[off]</code> and elements <code>b[off+len]</code> through
* <code>b[b.length-1]</code> are unaffected.
*
* <p> If the first byte cannot be read for any reason other than end of
* file, then an <code>IOException</code> is thrown. In particular, an
* <code>IOException</code> is thrown if the input stream has been closed.
*
* <p> The <code>read(b,</code> <code>off,</code> <code>len)</code> method
* for class <code>InputStream</code> simply calls the method
* <code>read()</code> repeatedly. If the first such call results in an
* <code>IOException</code>, that exception is returned from the call to
* the <code>read(b,</code> <code>off,</code> <code>len)</code> method. If
* any subsequent call to <code>read()</code> results in a
* <code>IOException</code>, the exception is caught and treated as if it
* were end of file; the bytes read up to that point are stored into
* <code>b</code> and the number of bytes read before the exception
* occurred is returned. Subclasses are encouraged to provide a more
* efficient implementation of this method.
*
* @param p_b the buffer into which the data is read.
* @param p_off the start offset in array <code>b</code>
* at which the data is written.
* @param p_len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
* @exception NullPointerException if <code>b</code> is <code>null</code>.
* @see java.io.InputStream#read()
*/
@Override
public final int read(final byte[] p_b,
int p_off,
int p_len) throws IOException
{
int l_r, l_s;
byte[] l_b;
if(p_len <= 0) return 0;
l_b = this.m_buffer;
l_s = 0;
while(p_len > 0)
{
l_r = this.m_available;
if(l_r > p_len) l_r = p_len;
System.arraycopy(l_b, this.m_pos, p_b, p_off, l_r);
l_s += l_r;
if((this.m_available -= l_r) <= 0)
{
if(!(this.fill_buffer(l_b))) break;
}
else
{
this.m_pos += l_r;
break;
}
p_off += l_r;
p_len -= l_r;
}
return ((l_s > 0) ? l_s : -1);
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
@Override
public final int read() throws IOException
{
byte[] l_b;
byte l_r;
l_b = this.m_buffer;
if( (this.m_available > 0) || this.fill_buffer(l_b) )
{
l_r = l_b[this.m_pos++];
if( (--this.m_available) <= 0 ) this.fill_buffer(l_b);
return l_r;
}
return -1;
}
}