package LBJ2.io;
import java.io.IOException;
import java.io.InputStream;
/**
* This class receives input from another <code>InputStream</code> assuming
* that data is little endian, hexidecimal text, converts that text to bytes,
* and makes those bytes available through its interface. The most common
* usage of this class will involve passing it to the constructor of another
* <code>InputStream</code>. For instance: <br><br>
*
* <pre>
* ObjectInputStream ois =
* new ObjectInputStream(
* new GZIPInputStream(
* new HexInputStream(new FileInputStream(fileName))));
* </pre>
*
* @see HexOutputStream
* @author Nick Rizzolo
**/
public class HexInputStream extends InputStream
{
/** Characters representing a hexidecimal digit. */
private static final String digits = "0123456789ABCDEF";
/**
* The <code>InputStream</code> from which yet-to-be-converted input
* should be received.
**/
private InputStream in;
/**
* Initializes this stream with another input stream.
*
* @param i The input stream from which yet-to-be-converted input should
* be received.
**/
public HexInputStream(InputStream i) { in = i; }
/**
* Reads the next byte of data from the input stream. The value is
* returned as an <code>int</code> in the range 0 to 255. If no byte is
* available because the end of the stream has been reached, the value -1
* is returned. This method blocks until input data is available, the end
* of the stream is detected, or an exception is thrown.
*
* @return The next byte of data, or -1 if the end of the stream is
* reached.
**/
public int read() throws IOException {
int d1 = in.read();
if (d1 == -1) return -1;
int d2 = in.read();
if (d2 == -1)
throw new IOException("HexInputStream: Unexpected end of file");
int i1 = digits.indexOf((char) d1);
if (i1 == -1)
throw new IOException("HexInputStream: Invalid input character: '"
+ ((char) d1) + "' (" + d1 + ")");
int i2 = digits.indexOf((char) d2);
if (i2 == -1)
throw new IOException("HexInputStream: Invalid input character: '"
+ ((char) d2) + "' (" + d2 + ")");
return (i2 << 4) | i1;
}
/**
* This method has the same effect as <code>read(b, 0, b.length)</code>.
*
* @param b A buffer in which the converted input is stored.
* @return The total number of bytes read into the buffer, or -1 if there
* is no more data because the end of the stream was previously
* reached.
**/
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from another 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, possibly zero.
* The number of bytes actually read is returned as an integer. <br><br>
*
* This method blocks until input data is available, end of file is
* detected, or an exception is thrown. <br><br>
*
* If <code>b</code> is <code>null</code>, a
* <code>NullPointerException</code> is thrown. <br><br>
*
* 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. <br><br>
*
* If <code>len</code> is zero, then no bytes are read and 0 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 -1 is
* returned; otherwise, at least one byte is read and stored into
* <code>b</code>. <br><br>
*
* 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+k-1]</code>, leaving elements
* <code>b[off+k]</code> through <code>b[off+len-1]</code> unaffected.
* <br><br>
*
* In every case, elements <code>b[0]</code> through <code>b[off-1]</code>
* and elements <code>b[off+len]</code> through <code>b[b.length-1]</code>
* are unaffected. <br><br>
*
* 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.
*
* @param b A buffer into which the converted input is stored.
* @param off The offset in the buffer at which to begin writing.
* @param len The amount of bytes to be received and written into the
* buffer.
* @return The total number of bytes read into the buffer, or -1 if there
* is no more data because the end of the stream has been reached.
**/
public int read(byte[] b, int off, int len) throws IOException {
byte[] hex = new byte[2 * len];
int bytesRead = in.read(hex);
if (bytesRead == -1) return -1;
if (bytesRead % 2 == 1)
throw new IOException("HexInputStream: Unexpected end of file");
for (int i = 0; i < bytesRead; i += 2) {
int d1 = digits.indexOf((char) hex[i]);
if (d1 == -1)
throw new IOException("HexInputStream: Invalid input character: '"
+ ((char) hex[i]) + "' (" + ((int) hex[i])
+ ")");
int d2 = digits.indexOf((char) hex[i + 1]);
if (d2 == -1)
throw new IOException("HexInputStream: Invalid input character: '"
+ ((char) hex[i + 1]) + "' ("
+ ((int) hex[i + 1]) + ")");
b[i / 2] = (byte) ((d2 << 4) | d1);
}
return bytesRead / 2;
}
/**
* Skips over and discards <code>n</code> bytes of data from this input
* stream. The skip method may, for a variety of reasons, end up skipping
* over some smaller number of bytes, possibly 0. This may result from any
* of a number of conditions; reaching end of file before <code>n</code>
* bytes have been skipped is only one possibility. The actual number of
* bytes skipped is returned. If <code>n</code> is negative, no bytes are
* skipped.
*
* @param n The number of bytes to be skipped.
* @return The actual number of bytes skipped.
**/
public long skip(long n) throws IOException { return in.skip(n * 2); }
/**
* Returns the number of bytes that can be read (or skipped over) from this
* input stream without blocking by the next caller of a method for this
* input stream. The next caller might be the same thread or or another
* thread.
*
* @return The number of bytes that can be read from this input stream
* without blocking.
**/
public int available() throws IOException { return in.available() / 2; }
/**
* Closes this input stream and releases any system resources associated
* with the stream.
**/
public void close() throws IOException { in.close(); }
/**
* Marks the current position in this input stream. A subsequent call to
* the <code>reset</code> method repositions this stream at the last marked
* position so that subsequent reads re-read the same bytes.
*
* The <code>readlimit</code> argument tells this input stream to allow
* that many bytes to be read before the mark position gets invalidated.
*
* The general contract of mark is that, if the method
* <code>markSupported</code> returns <code>true</code>, the stream somehow
* remembers all the bytes read after the call to mark and stands ready to
* supply those same bytes again if and whenever the method
* <code>reset</code> is called. However, the stream is not required to
* remember any data at all if more than <code>readlimit</code> bytes are
* read from the stream before <code>reset</code> is called.
*
* @param readlimit The maximum limit of bytes that can be read before the
* mark position becomes invalid.
**/
public void mark(int readlimit) { in.mark(readlimit * 2); }
/**
* Repositions this stream to the position at the time the
* <code>mark</code> method was last called on this input stream.
**/
public void reset() throws IOException { in.reset(); }
/**
* Tests if this input stream supports the mark and reset methods. Whether
* or not <code>mark</code> and <code>reset</code> are supported is an
* invariant property of the provided input stream instance.
*
* @return <code>true</code> iff the provided input stream instance
* supports the <code>mark</code> and <code>reset</code> methods.
**/
public boolean markSupported() { return in.markSupported(); }
}