package LBJ2.util;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
* This class intends to operate just as a <code>DataInputStream</code> with
* some additional convenience methods and built-in exception handling.
*
* @author Nick Rizzolo
**/
public class ExceptionlessInputStream extends FilterInputStream
{
/** The entry inside any compressed file has this name. */
public static final String zipEntryName = "LBJFile";
/** This buffer is used internally by {@link #readUTF(int)}. */
private byte[] buffer = null;
/** This buffer is used internally by {@link #readUTF(int)}. */
private char[] chars = null;
/** The underlying data input stream. */
private DataInputStream dis;
/**
* Opens a buffered (and uncompressed) stream for reading from the
* specified file.
*
* @param filename The file to read from.
* @return The newly opened stream.
**/
public static ExceptionlessInputStream openBufferedStream(String filename) {
ExceptionlessInputStream eis = null;
try {
eis =
new ExceptionlessInputStream(
new BufferedInputStream(
new FileInputStream(filename)));
}
catch (Exception e) {
System.err.println("Can't open '" + filename + "' for input:");
e.printStackTrace();
System.exit(1);
}
return eis;
}
/**
* Opens a compressed stream for reading from the specified file.
*
* @param filename The file to read from.
* @return The newly opened stream.
**/
public static ExceptionlessInputStream openCompressedStream(String filename)
{
ExceptionlessInputStream eis = null;
try {
ZipFile zip = new ZipFile(filename);
eis =
new ExceptionlessInputStream(
new BufferedInputStream(
zip.getInputStream(zip.getEntry(zipEntryName))));
}
catch (Exception e) {
System.err.println("Can't open '" + filename + "' for input:");
e.printStackTrace();
System.exit(1);
}
return eis;
}
/**
* Opens a buffered (and uncompressed) stream for reading from the
* specified location.
*
* @param url The location to read from.
* @return The newly opened stream.
**/
public static ExceptionlessInputStream openBufferedStream(URL url) {
ExceptionlessInputStream eis = null;
try {
eis =
new ExceptionlessInputStream(
new BufferedInputStream(url.openStream()));
}
catch (Exception e) {
System.err.println("Can't open '" + url + "' for input:");
e.printStackTrace();
System.exit(1);
}
return eis;
}
/**
* Opens a compressed stream for reading from the specified location.
*
* @param url The location to read from.
* @return The newly opened stream.
**/
public static ExceptionlessInputStream openCompressedStream(URL url) {
if (url.getProtocol().equals("file"))
return openCompressedStream(url.getFile());
ExceptionlessInputStream eis = null;
try {
ZipInputStream zip = new ZipInputStream(url.openStream());
zip.getNextEntry();
eis = new ExceptionlessInputStream(new BufferedInputStream(zip));
}
catch (Exception e) {
System.err.println("Can't open '" + url + "' for input:");
e.printStackTrace();
System.exit(1);
}
return eis;
}
/**
* Creates a new data input stream to read data from the specified
* underlying input stream.
*
* @param in The underlying input stream.
**/
public ExceptionlessInputStream(InputStream in) {
super(new DataInputStream(in));
dis = (DataInputStream) this.in;
}
/**
* Whenever an exception is caught, this method attempts to close the
* stream and exit the program.
*
* @param e The thrown exception.
**/
private void handleException(Exception e) {
System.err.println("Can't read from input stream:");
e.printStackTrace();
close();
System.exit(1);
}
/**
* Closes this input stream and releases any system resources associated
* with the stream.
**/
public void close() {
try { dis.close(); }
catch (Exception e) {
System.err.println("Can't close input stream:");
e.printStackTrace();
System.exit(1);
}
}
/**
* Reads one input byte and returns <code>true</code> if that byte is
* nonzero, <code>false</code> if that byte is zero. This method is
* suitable for reading the byte written by
* {@link ExceptionlessOutputStream#writeBoolean(boolean)}.
*
* @return The <code>boolean</code> value read.
**/
public boolean readBoolean() {
try { return dis.readBoolean(); }
catch (Exception e) { handleException(e); }
return false;
}
/**
* Reads and returns one input byte. The byte is treated as a signed value
* in the range <code>-128</code> through <code>127</code>, inclusive.
* This method is suitable for reading the byte written by
* {@link ExceptionlessOutputStream#writeByte(int)}.
*
* @return The 8-bit value read.
**/
public byte readByte() {
try { return dis.readByte(); }
catch (Exception e) { handleException(e); }
return (byte) 0;
}
/**
* Reads and returns an array of bytes from the input. The input stream is
* expected to contain an integer representing the number of bytes in the
* array first, followed by the bytes in the array. Each byte is treated
* as a signed value in the range <code>-128</code> through
* <code>127</code>, inclusive. This method is suitable for reading the
* byte array written by
* {@link ExceptionlessOutputStream#writeBytes(byte[])}.
*
* @return The array of 8-bit bytes read.
**/
public byte[] readBytes() {
try {
int n = dis.readInt();
if (n < 0) return null;
byte[] result = new byte[n];
for (int i = 0; i < n; ++i)
result[i] = dis.readByte();
return result;
}
catch (Exception e) { handleException(e); }
return null;
}
/**
* Reads one input byte, zero-extends it to type <code>int</code>, and
* returns the result, which is therefore in the range <code>0</code>
* through <code>255</code>. This method is suitable for reading the byte
* written by {@link ExceptionlessOutputStream#writeByte(int)} if the
* argument to <code>writeByte</code> was intended to be a value in the
* range <code>0</code> through <code>255</code>.
*
* @return The unsigned 8-bit value read.
**/
public int readUnsignedByte() {
try { return dis.readByte(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads two input bytes and returns a <code>short</code> value. Let
* <code>a</code> be the first byte read and <code>b</code> be the second
* byte. The value returned is:
* <p><pre><code>(short)((a << 8) | (b & 0xff))
* </code></pre>
* This method is suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeShort(int)}.
*
* @return The 16-bit value read.
**/
public short readShort() {
try { return dis.readShort(); }
catch (Exception e) { handleException(e); }
return (short) 0;
}
/**
* Reads two input bytes and returns an <code>int</code> value in the range
* <code>0</code> through <code>65535</code>. Let <code>a</code> be the
* first byte read and <code>b</code> be the second byte. The value
* returned is:
* <p><pre><code>(((a & 0xff) << 8) | (b & 0xff))
* </code></pre>
* This method is suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeShort(int)} if the argument to
* <code>writeShort</code> was intended to be a value in the range
* <code>0</code> through <code>65535</code>.
*
* @return The unsigned 16-bit value read.
**/
public int readUnsignedShort() {
try { return dis.readUnsignedShort(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads an input <code>char</code> and returns the <code>char</code>
* value. A Unicode <code>char</code> is made up of two bytes. Let
* <code>a</code> be the first byte read and <code>b</code> be the second
* byte. The value returned is:
* <p><pre><code>(char)((a << 8) | (b & 0xff))
* </code></pre>
* This method is suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeChar(int)}.
*
* @return The Unicode <code>char</code> read.
**/
public char readChar() {
try { return dis.readChar(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads four input bytes and returns an <code>int</code> value. Let
* <code>a</code> be the first byte read, <code>b</code> be the second
* byte, <code>c</code> be the third byte, and <code>d</code> be the fourth
* byte. The value returned is:
* <p><pre>
* <code>
* (((a & 0xff) << 24) | ((b & 0xff) << 16) |
* ((c & 0xff) << 8) | (d & 0xff))
* </code></pre>
* This method is suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeInt(int)}.
*
* @return The <code>int</code> value read.
**/
public int readInt() {
try { return dis.readInt(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads eight input bytes and returns a <code>long</code> value. Let
* <code>a</code> be the first byte read, <code>b</code> be the second
* byte, <code>c</code> be the third byte, <code>d</code> be the fourth
* byte, <code>e</code> be the fifth byte, <code>f</code> be the sixth
* byte, <code>g</code> be the seventh byte, and <code>h</code> be the
* eighth byte. The value returned is:
* <p><pre> <code>
* (((long)(a & 0xff) << 56) |
* ((long)(b & 0xff) << 48) |
* ((long)(c & 0xff) << 40) |
* ((long)(d & 0xff) << 32) |
* ((long)(e & 0xff) << 24) |
* ((long)(f & 0xff) << 16) |
* ((long)(g & 0xff) << 8) |
* ((long)(h & 0xff)))
* </code></pre>
* <p>
* This method is suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeLong(long)}.
*
* @return The <code>long</code> value read.
**/
public long readLong() {
try { return dis.readLong(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads four input bytes and returns a <code>float</code> value. It does
* this by first constructing an <code>int</code> value in exactly the
* manner of the <code>readInt</code> method, then converting this
* <code>int</code> value to a <code>float</code> in exactly the manner of
* the method <code>Float.intBitsToFloat</code>. This method is suitable
* for reading the bytes written by
* {@link ExceptionlessOutputStream#writeFloat(float)}.
*
* @return The <code>float</code> value read.
**/
public float readFloat() {
try { return dis.readFloat(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads eight input bytes and returns a <code>double</code> value. It
* does this by first constructing a <code>long</code> value in exactly the
* manner of the <code>readlong</code> method, then converting this
* <code>long</code> value to a <code>double</code> in exactly the manner
* of the method <code>Double.longBitsToDouble</code>. This method is
* suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeDouble(double)}.
*
* @return The <code>double</code> value read.
**/
public double readDouble() {
try { return dis.readDouble(); }
catch (Exception e) { handleException(e); }
return 0;
}
/**
* Reads a string from the underlying stream.
*
* @return The string.
**/
public String readString() {
short utfLength = readShort();
if (utfLength == -1) return null;
String result = readUTF(utfLength);
return result;
}
/**
* Reads in a string that has been encoded using a
* <a href="http://java.sun.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8">modified UTF-8</a>
* format. This method is suitable for reading the bytes written by
* {@link ExceptionlessOutputStream#writeUTF(String)}.
*
* @param utfLength The number of bytes expected in the encoding of the
* string to read.
* @return A Unicode string.
**/
public String readUTF(int utfLength) {
if (buffer == null || buffer.length < utfLength) {
buffer = new byte[utfLength * 2];
chars = new char[utfLength * 2];
}
int c, char2, char3;
int count = 0;
int charsCount = 0;
try { dis.readFully(buffer, 0, utfLength); }
catch (Exception e) { handleException(e); }
while (count < utfLength) {
c = (int) buffer[count] & 0xff;
if (c > 127) break;
count++;
chars[charsCount++] = (char) c;
}
while (count < utfLength) {
c = (int) buffer[count] & 0xff;
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
/* 0xxxxxxx*/
count++;
chars[charsCount++] = (char) c;
break;
case 12: case 13:
/* 110x xxxx 10xx xxxx*/
count += 2;
if (count > utfLength) {
System.err.println(
"Error in UTF formatting: partial character at end");
new Exception().printStackTrace();
close();
System.exit(1);
}
char2 = (int) buffer[count - 1];
if ((char2 & 0xC0) != 0x80) {
System.err.println(
"Error in UTF formatting: malformed input around byte "
+ count);
new Exception().printStackTrace();
close();
System.exit(1);
}
chars[charsCount++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
/* 1110 xxxx 10xx xxxx 10xx xxxx */
count += 3;
if (count > utfLength) {
System.err.println(
"Error in UTF formatting: partial character at end");
new Exception().printStackTrace();
close();
System.exit(1);
}
char2 = (int) buffer[count - 2];
char3 = (int) buffer[count - 1];
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) {
System.err.println(
"Error in UTF formatting: malformed input around byte "
+ (count - 1));
new Exception().printStackTrace();
close();
System.exit(1);
}
chars[charsCount++] = (char) (((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
default:
/* 10xx xxxx, 1111 xxxx */
System.err.println(
"Error in UTF formatting: malformed input around byte "
+ count);
new Exception().printStackTrace();
close();
System.exit(1);
}
}
// The number of chars produced may be less than utfLength
return new String(chars, 0, charsCount);
}
}