package LBJ2.io; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; /** * Behaves the same as <code>HexInputStream</code>, except its * constructor takes a <code>String</code> as input to read. * * @see HexInputStream * @author Nick Rizzolo **/ public class HexStringInputStream extends InputStream { /** Characters representing a hexidecimal digit. */ private static final String digits = "0123456789ABCDEF"; /** Reads encoded input from a given string. */ private StringReader in; /** * Initializes this stream with another input stream. * * @param s The string from which yet-to-be-converted input should be * received. **/ public HexStringInputStream(String s) { in = new StringReader(s); } /** * Reads the next char of data from the input stream. The value is * returned as an <code>int</code> in the range 0 to 255. If no char 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 char 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("HexStringInputStream: Unexpected end of file"); int i1 = digits.indexOf((char) d1); if (i1 == -1) throw new IOException("HexStringInputStream: Invalid input character: '" + ((char) d1) + "' (" + d1 + ")"); int i2 = digits.indexOf((char) d2); if (i2 == -1) throw new IOException("HexStringInputStream: 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 chars read into the buffer, or -1 if there * is no more data because the end of the stream was previously * reached. **/ public int read(char[] b) throws IOException { return read(b, 0, b.length); } /** * Reads up to <code>len</code> chars of data from another String * into an array of chars. An attempt is made to read as many as * <code>len</code> chars, but a smaller number may be read, possibly zero. * The number of chars 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 chars are read and 0 is returned; * otherwise, there is an attempt to read at least one char. If no char is * available because the stream is at end of file, the value -1 is * returned; otherwise, at least one char is read and stored into * <code>b</code>. <br><br> * * The first char read is stored into element <code>b[off]</code>, the next * one into <code>b[off+1]</code>, and so on. The number of chars read is, * at most, equal to <code>len</code>. Let <i>k</i> be the number of chars * actually read; these chars 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 char 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 chars to be received and written into the * buffer. * @return The total number of chars read into the buffer, or -1 if there * is no more data because the end of the stream has been reached. **/ public int read(char[] b, int off, int len) throws IOException { char[] hex = new char[2 * len]; int charsRead = in.read(hex); if (charsRead == -1) return -1; if (charsRead % 2 == 1) throw new IOException("HexStringInputStream: Unexpected end of file"); for (int i = 0; i < charsRead; i += 2) { int d1 = digits.indexOf((char) hex[i]); if (d1 == -1) throw new IOException( "HexStringInputStream: Invalid input character: '" + ((char) hex[i]) + "' (" + ((int) hex[i]) + ")"); int d2 = digits.indexOf((char) hex[i + 1]); if (d2 == -1) throw new IOException( "HexStringInputStream: Invalid input character: '" + ((char) hex[i + 1]) + "' (" + ((int) hex[i + 1]) + ")"); b[i / 2] = (char) ((d2 << 4) | d1); } return charsRead / 2; } /** * Skips over and discards <code>n</code> chars of data from this input * stream. The skip method may, for a variety of reasons, end up skipping * over some smaller number of chars, possibly 0. This may result from any * of a number of conditions; reaching end of file before <code>n</code> * chars have been skipped is only one possibility. The actual number of * chars skipped is returned. If <code>n</code> is negative, no chars are * skipped. * * @param n The number of chars to be skipped. * @return The actual number of chars skipped. **/ public long skip(long n) throws IOException { return in.skip(n * 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 chars. * * The <code>readlimit</code> argument tells this input stream to allow * that many chars 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 chars read after the call to mark and stands ready to * supply those same chars 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> chars are * read from the stream before <code>reset</code> is called. * * @param readlimit The maximum limit of chars that can be read before the * mark position becomes invalid. **/ public void mark(int readlimit) { try { in.mark(readlimit * 2); } catch (Exception e) { System.err.println(e); } } /** * 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(); } }