diff --git a/src/main/java/org/warp/commonutils/stream/SafeDataInputStream.java b/src/main/java/org/warp/commonutils/stream/SafeDataInputStream.java new file mode 100644 index 0000000..3e84e5c --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeDataInputStream.java @@ -0,0 +1,580 @@ +/* + * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.warp.commonutils.stream; + +import java.io.DataInput; + +/** + * A data input stream lets an application read primitive Java data + * types from an underlying input stream in a machine-independent + * way. An application uses a data output stream to write data that + * can later be read by a data input stream. + *

+ * DataInputStream is not necessarily safe for multithreaded access. + * Thread safety is optional and is the responsibility of users of + * methods in this class. + * + * @author Arthur van Hoff + * @see java.io.DataOutputStream + * @since 1.0 + */ +public class SafeDataInputStream extends SafeFilterInputStream implements DataInput { + + /** + * Creates a DataInputStream that uses the specified + * underlying InputStream. + * + * @param in the specified input stream + */ + public SafeDataInputStream(SafeInputStream in) { + super(in); + } + + /** + * working arrays initialized on demand by readUTF + */ + private byte bytearr[] = new byte[80]; + private char chararr[] = new char[80]; + + /** + * Reads some number of bytes from the contained input stream and + * stores them into the buffer array {@code b}. The number of + * bytes actually read is returned as an integer. This method blocks + * until input data is available, end of file is detected, or an + * exception is thrown. + * + *

If {@code b} is null, a {@code NullPointerException} is + * thrown. If the length of {@code b} is zero, then no bytes are + * read and {@code 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 {@code -1} is returned; + * otherwise, at least one byte is read and stored into {@code b}. + * + *

The first byte read is stored into element {@code b[0]}, the + * next one into {@code b[1]}, and so on. The number of bytes read + * is, at most, equal to the length of {@code b}. Let {@code k} + * be the number of bytes actually read; these bytes will be stored in + * elements {@code b[0]} through {@code b[k-1]}, leaving + * elements {@code b[k]} through {@code b[b.length-1]} + * unaffected. + * + *

The {@code read(b)} method has the same effect as: + *

+	 * read(b, 0, b.length)
+	 * 
+ * + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end + * of the stream has been reached. + * @see SafeFilterInputStream#in + * @see java.io.InputStream#read(byte[], int, int) + */ + public final int read(byte b[]) { + return in.read(b, 0, b.length); + } + + /** + * Reads up to {@code len} bytes of data from the contained + * input stream into an array of bytes. An attempt is made to read + * as many as {@code len} bytes, but a smaller number may be read, + * possibly zero. The number of bytes actually read is returned as an + * integer. + * + *

This method blocks until input data is available, end of file is + * detected, or an exception is thrown. + * + *

If {@code len} is zero, then no bytes are read and + * {@code 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 {@code -1} is returned; otherwise, at least one + * byte is read and stored into {@code b}. + * + *

The first byte read is stored into element {@code b[off]}, the + * next one into {@code b[off+1]}, and so on. The number of bytes read + * is, at most, equal to {@code len}. Let k be the number of + * bytes actually read; these bytes will be stored in elements + * {@code b[off]} through {@code b[off+}k{@code -1]}, + * leaving elements {@code b[off+}k{@code ]} through + * {@code b[off+len-1]} unaffected. + * + *

In every case, elements {@code b[0]} through + * {@code b[off]} and elements {@code b[off+len]} through + * {@code b[b.length-1]} are unaffected. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end + * of the stream has been reached. + * @throws NullPointerException If {@code b} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + * @see SafeFilterInputStream#in + * @see java.io.InputStream#read(byte[], int, int) + */ + public final int read(byte b[], int off, int len) { + return in.read(b, off, len); + } + + /** + * See the general contract of the {@code readFully} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @param b the buffer into which the data is read. + * @throws NullPointerException if {@code b} is {@code null}. + * @see SafeFilterInputStream#in + */ + public final void readFully(byte b[]) { + readFully(b, 0, b.length); + } + + /** + * See the general contract of the {@code readFully} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the data array {@code b}. + * @param len the number of bytes to read. + * @throws NullPointerException if {@code b} is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off}. + * @see SafeFilterInputStream#in + */ + public final void readFully(byte b[], int off, int len) { + if (len < 0) + throw new IndexOutOfBoundsException(); + int n = 0; + while (n < len) { + int count = in.read(b, off + n, len - n); + if (count < 0) + throw new IndexOutOfBoundsException(); + n += count; + } + } + + /** + * See the general contract of the {@code skipBytes} + * method of {@code DataInput}. + *

+ * Bytes for this operation are read from the contained + * input stream. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + */ + public final int skipBytes(int n) { + int total = 0; + int cur = 0; + + while ((total 0)) { + total += cur; + } + + return total; + } + + /** + * See the general contract of the {@code readBoolean} + * method of {@code DataInput}. + *

+ * Bytes for this operation are read from the contained + * input stream. + * + * @return the {@code boolean} value read. + * @see SafeFilterInputStream#in + */ + public final boolean readBoolean() { + int ch = in.read(); + if (ch < 0) + throw new IndexOutOfBoundsException(); + return (ch != 0); + } + + /** + * See the general contract of the {@code readByte} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next byte of this input stream as a signed 8-bit + * {@code byte}. + * @see SafeFilterInputStream#in + */ + public final byte readByte() { + int ch = in.read(); + if (ch < 0) + throw new IndexOutOfBoundsException(); + return (byte)(ch); + } + + /** + * See the general contract of the {@code readUnsignedByte} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next byte of this input stream, interpreted as an + * unsigned 8-bit number. + * @see SafeFilterInputStream#in + */ + public final int readUnsignedByte() { + int ch = in.read(); + if (ch < 0) + throw new IndexOutOfBoundsException(); + return ch; + } + + /** + * See the general contract of the {@code readShort} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next two bytes of this input stream, interpreted as a + * signed 16-bit number. + * @see SafeFilterInputStream#in + */ + public final short readShort() { + int ch1 = in.read(); + int ch2 = in.read(); + if ((ch1 | ch2) < 0) + throw new IndexOutOfBoundsException(); + return (short)((ch1 << 8) + (ch2 << 0)); + } + + /** + * See the general contract of the {@code readUnsignedShort} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next two bytes of this input stream, interpreted as an + * unsigned 16-bit integer. + * @see SafeFilterInputStream#in + */ + public final int readUnsignedShort() { + int ch1 = in.read(); + int ch2 = in.read(); + if ((ch1 | ch2) < 0) + throw new IndexOutOfBoundsException(); + return (ch1 << 8) + (ch2 << 0); + } + + /** + * See the general contract of the {@code readChar} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next two bytes of this input stream, interpreted as a + * {@code char}. + * @see SafeFilterInputStream#in + */ + public final char readChar() { + int ch1 = in.read(); + int ch2 = in.read(); + if ((ch1 | ch2) < 0) + throw new IndexOutOfBoundsException(); + return (char)((ch1 << 8) + (ch2 << 0)); + } + + /** + * See the general contract of the {@code readInt} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next four bytes of this input stream, interpreted as an + * {@code int}. + * @see SafeFilterInputStream#in + */ + public final int readInt() { + int ch1 = in.read(); + int ch2 = in.read(); + int ch3 = in.read(); + int ch4 = in.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new IndexOutOfBoundsException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + private byte readBuffer[] = new byte[8]; + + /** + * See the general contract of the {@code readLong} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next eight bytes of this input stream, interpreted as a + * {@code long}. + * @see SafeFilterInputStream#in + */ + public final long readLong() { + readFully(readBuffer, 0, 8); + return (((long)readBuffer[0] << 56) + + ((long)(readBuffer[1] & 255) << 48) + + ((long)(readBuffer[2] & 255) << 40) + + ((long)(readBuffer[3] & 255) << 32) + + ((long)(readBuffer[4] & 255) << 24) + + ((readBuffer[5] & 255) << 16) + + ((readBuffer[6] & 255) << 8) + + ((readBuffer[7] & 255) << 0)); + } + + /** + * See the general contract of the {@code readFloat} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next four bytes of this input stream, interpreted as a + * {@code float}. + * @see SafeDataInputStream#readInt() + * @see java.lang.Float#intBitsToFloat(int) + */ + public final float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + /** + * See the general contract of the {@code readDouble} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return the next eight bytes of this input stream, interpreted as a + * {@code double}. + * @see SafeDataInputStream#readLong() + * @see java.lang.Double#longBitsToDouble(long) + */ + public final double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + private char lineBuffer[]; + + /** + * See the general contract of the {@code readLine} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @deprecated This method does not properly convert bytes to characters. + * As of JDK 1.1, the preferred way to read lines of text is via the + * {@code BufferedReader.readLine()} method. Programs that use the + * {@code DataInputStream} class to read lines can be converted to use + * the {@code BufferedReader} class by replacing code of the form: + *

+	 *     DataInputStream d = new DataInputStream(in);
+	 * 
+ * with: + *
+	 *     BufferedReader d
+	 *          = new BufferedReader(new InputStreamReader(in));
+	 * 
+ * + * @return the next line of text from this input stream. + * @see java.io.BufferedReader#readLine() + * @see SafeFilterInputStream#in + */ + @Deprecated + public final String readLine() { + char buf[] = lineBuffer; + + if (buf == null) { + buf = lineBuffer = new char[128]; + } + + int room = buf.length; + int offset = 0; + int c; + + loop: while (true) { + switch (c = in.read()) { + case -1: + case '\n': + break loop; + + case '\r': + int c2 = in.read(); + if ((c2 != '\n') && (c2 != -1)) { + if (!(in instanceof SafePushbackInputStream)) { + this.in = new SafePushbackInputStream(in); + } + ((SafePushbackInputStream)in).unread(c2); + } + break loop; + + default: + if (--room < 0) { + buf = new char[offset + 128]; + room = buf.length - offset - 1; + System.arraycopy(lineBuffer, 0, buf, 0, offset); + lineBuffer = buf; + } + buf[offset++] = (char) c; + break; + } + } + if ((c == -1) && (offset == 0)) { + return null; + } + return String.copyValueOf(buf, 0, offset); + } + + /** + * See the general contract of the {@code readUTF} + * method of {@code DataInput}. + *

+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return a Unicode string. + * @see SafeDataInputStream#readUTF(SafeDataInputStream) + */ + public final String readUTF() { + return readUTF(this); + } + + /** + * Reads from the + * stream {@code in} a representation + * of a Unicode character string encoded in + * modified UTF-8 format; + * this string of characters is then returned as a {@code String}. + * The details of the modified UTF-8 representation + * are exactly the same as for the {@code readUTF} + * method of {@code DataInput}. + * + * @param in a data input stream. + * @return a Unicode string. + * @see SafeDataInputStream#readUnsignedShort() + */ + public static final String readUTF(SafeDataInputStream in) { + int utflen = in.readUnsignedShort(); + byte[] bytearr = null; + char[] chararr = null; + SafeDataInputStream dis = in; + if (dis.bytearr.length < utflen){ + dis.bytearr = new byte[utflen*2]; + dis.chararr = new char[utflen*2]; + } + chararr = dis.chararr; + bytearr = dis.bytearr; + + int c, char2, char3; + int count = 0; + int chararr_count=0; + + in.readFully(bytearr, 0, utflen); + + while (count < utflen) { + c = (int) bytearr[count] & 0xff; + if (c > 127) break; + count++; + chararr[chararr_count++]=(char)c; + } + + while (count < utflen) { + c = (int) bytearr[count] & 0xff; + switch (c >> 4) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + /* 0xxxxxxx*/ + count++; + chararr[chararr_count++]=(char)c; + break; + case 12: case 13: + /* 110x xxxx 10xx xxxx*/ + count += 2; + if (count > utflen) + throw new IllegalArgumentException( + "malformed input: partial character at end"); + char2 = bytearr[count-1]; + if ((char2 & 0xC0) != 0x80) + throw new IllegalArgumentException( + "malformed input around byte " + count); + chararr[chararr_count++]=(char)(((c & 0x1F) << 6) | + (char2 & 0x3F)); + break; + case 14: + /* 1110 xxxx 10xx xxxx 10xx xxxx */ + count += 3; + if (count > utflen) + throw new IllegalArgumentException( + "malformed input: partial character at end"); + char2 = bytearr[count-2]; + char3 = bytearr[count-1]; + if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) + throw new IllegalArgumentException( + "malformed input around byte " + (count-1)); + chararr[chararr_count++]=(char)(((c & 0x0F) << 12) | + ((char2 & 0x3F) << 6) | + ((char3 & 0x3F) << 0)); + break; + default: + /* 10xx xxxx, 1111 xxxx */ + throw new IllegalArgumentException( + "malformed input around byte " + count); + } + } + // The number of chars produced may be less than utflen + return new String(chararr, 0, chararr_count); + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeDataOutput.java b/src/main/java/org/warp/commonutils/stream/SafeDataOutput.java new file mode 100644 index 0000000..a48aa34 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeDataOutput.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.warp.commonutils.stream; + +/** + * The {@code SafeDataOutput} interface provides + * for converting data from any of the Java + * primitive types to a series of bytes and + * writing these bytes to a binary stream. + * There is also a facility for converting + * a {@code String} into + * modified UTF-8 + * format and writing the resulting series + * of bytes. + *

+ * For all the methods in this interface that + * write bytes, it is generally true that if + * a byte cannot be written for any reason, + * an {@code IOException} is thrown. + * + * @author Frank Yellin + * @see java.io.DataInput + * @see java.io.DataOutputStream + * @since 1.0 + */ +public interface SafeDataOutput { + /** + * Writes to the output stream the eight + * low-order bits of the argument {@code b}. + * The 24 high-order bits of {@code b} + * are ignored. + * + * @param b the byte to be written. + */ + void write(int b); + + /** + * Writes to the output stream all the bytes in array {@code b}. + * If {@code b} is {@code null}, + * a {@code NullPointerException} is thrown. + * If {@code b.length} is zero, then + * no bytes are written. Otherwise, the byte + * {@code b[0]} is written first, then + * {@code b[1]}, and so on; the last byte + * written is {@code b[b.length-1]}. + * + * @param b the data. + */ + void write(byte b[]); + + /** + * Writes {@code len} bytes from array + * {@code b}, in order, to + * the output stream. If {@code b} + * is {@code null}, a {@code NullPointerException} + * is thrown. If {@code off} is negative, + * or {@code len} is negative, or {@code off+len} + * is greater than the length of the array + * {@code b}, then an {@code IndexOutOfBoundsException} + * is thrown. If {@code len} is zero, + * then no bytes are written. Otherwise, the + * byte {@code b[off]} is written first, + * then {@code b[off+1]}, and so on; the + * last byte written is {@code b[off+len-1]}. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + */ + void write(byte b[], int off, int len); + + /** + * Writes a {@code boolean} value to this output stream. + * If the argument {@code v} + * is {@code true}, the value {@code (byte)1} + * is written; if {@code v} is {@code false}, + * the value {@code (byte)0} is written. + * The byte written by this method may + * be read by the {@code readBoolean} + * method of interface {@code DataInput}, + * which will then return a {@code boolean} + * equal to {@code v}. + * + * @param v the boolean to be written. + */ + void writeBoolean(boolean v); + + /** + * Writes to the output stream the eight low-order + * bits of the argument {@code v}. + * The 24 high-order bits of {@code v} + * are ignored. (This means that {@code writeByte} + * does exactly the same thing as {@code write} + * for an integer argument.) The byte written + * by this method may be read by the {@code readByte} + * method of interface {@code DataInput}, + * which will then return a {@code byte} + * equal to {@code (byte)v}. + * + * @param v the byte value to be written. + */ + void writeByte(int v); + + /** + * Writes two bytes to the output + * stream to represent the value of the argument. + * The byte values to be written, in the order + * shown, are: + *

{@code
+	 * (byte)(0xff & (v >> 8))
+	 * (byte)(0xff & v)
+	 * }

+ * The bytes written by this method may be + * read by the {@code readShort} method + * of interface {@code DataInput}, which + * will then return a {@code short} equal + * to {@code (short)v}. + * + * @param v the {@code short} value to be written. + */ + void writeShort(int v); + + /** + * Writes a {@code char} value, which + * is comprised of two bytes, to the + * output stream. + * The byte values to be written, in the order + * shown, are: + *

{@code
+	 * (byte)(0xff & (v >> 8))
+	 * (byte)(0xff & v)
+	 * }

+ * The bytes written by this method may be + * read by the {@code readChar} method + * of interface {@code DataInput}, which + * will then return a {@code char} equal + * to {@code (char)v}. + * + * @param v the {@code char} value to be written. + */ + void writeChar(int v); + + /** + * Writes an {@code int} value, which is + * comprised of four bytes, to the output stream. + * The byte values to be written, in the order + * shown, are: + *

{@code
+	 * (byte)(0xff & (v >> 24))
+	 * (byte)(0xff & (v >> 16))
+	 * (byte)(0xff & (v >>  8))
+	 * (byte)(0xff & v)
+	 * }

+ * The bytes written by this method may be read + * by the {@code readInt} method of interface + * {@code DataInput}, which will then + * return an {@code int} equal to {@code v}. + * + * @param v the {@code int} value to be written. + */ + void writeInt(int v); + + /** + * Writes a {@code long} value, which is + * comprised of eight bytes, to the output stream. + * The byte values to be written, in the order + * shown, are: + *

{@code
+	 * (byte)(0xff & (v >> 56))
+	 * (byte)(0xff & (v >> 48))
+	 * (byte)(0xff & (v >> 40))
+	 * (byte)(0xff & (v >> 32))
+	 * (byte)(0xff & (v >> 24))
+	 * (byte)(0xff & (v >> 16))
+	 * (byte)(0xff & (v >>  8))
+	 * (byte)(0xff & v)
+	 * }

+ * The bytes written by this method may be + * read by the {@code readLong} method + * of interface {@code DataInput}, which + * will then return a {@code long} equal + * to {@code v}. + * + * @param v the {@code long} value to be written. + */ + void writeLong(long v); + + /** + * Writes a {@code float} value, + * which is comprised of four bytes, to the output stream. + * It does this as if it first converts this + * {@code float} value to an {@code int} + * in exactly the manner of the {@code Float.floatToIntBits} + * method and then writes the {@code int} + * value in exactly the manner of the {@code writeInt} + * method. The bytes written by this method + * may be read by the {@code readFloat} + * method of interface {@code DataInput}, + * which will then return a {@code float} + * equal to {@code v}. + * + * @param v the {@code float} value to be written. + */ + void writeFloat(float v); + + /** + * Writes a {@code double} value, + * which is comprised of eight bytes, to the output stream. + * It does this as if it first converts this + * {@code double} value to a {@code long} + * in exactly the manner of the {@code Double.doubleToLongBits} + * method and then writes the {@code long} + * value in exactly the manner of the {@code writeLong} + * method. The bytes written by this method + * may be read by the {@code readDouble} + * method of interface {@code DataInput}, + * which will then return a {@code double} + * equal to {@code v}. + * + * @param v the {@code double} value to be written. + */ + void writeDouble(double v); + + /** + * Writes a string to the output stream. + * For every character in the string + * {@code s}, taken in order, one byte + * is written to the output stream. If + * {@code s} is {@code null}, a {@code NullPointerException} + * is thrown.

If {@code s.length} + * is zero, then no bytes are written. Otherwise, + * the character {@code s[0]} is written + * first, then {@code s[1]}, and so on; + * the last character written is {@code s[s.length-1]}. + * For each character, one byte is written, + * the low-order byte, in exactly the manner + * of the {@code writeByte} method . The + * high-order eight bits of each character + * in the string are ignored. + * + * @param s the string of bytes to be written. + */ + void writeBytes(String s); + + /** + * Writes every character in the string {@code s}, + * to the output stream, in order, + * two bytes per character. If {@code s} + * is {@code null}, a {@code NullPointerException} + * is thrown. If {@code s.length} + * is zero, then no characters are written. + * Otherwise, the character {@code s[0]} + * is written first, then {@code s[1]}, + * and so on; the last character written is + * {@code s[s.length-1]}. For each character, + * two bytes are actually written, high-order + * byte first, in exactly the manner of the + * {@code writeChar} method. + * + * @param s the string value to be written. + */ + void writeChars(String s); + + /** + * Writes two bytes of length information + * to the output stream, followed + * by the + * modified UTF-8 + * representation + * of every character in the string {@code s}. + * If {@code s} is {@code null}, + * a {@code NullPointerException} is thrown. + * Each character in the string {@code s} + * is converted to a group of one, two, or + * three bytes, depending on the value of the + * character.

+ * If a character {@code c} + * is in the range \u0001 through + * \u007f, it is represented + * by one byte: + *

(byte)c 

+ * If a character {@code c} is \u0000 + * or is in the range \u0080 + * through \u07ff, then it is + * represented by two bytes, to be written + * in the order shown:

{@code
+	 * (byte)(0xc0 | (0x1f & (c >> 6)))
+	 * (byte)(0x80 | (0x3f & c))
+	 * }

If a character + * {@code c} is in the range \u0800 + * through {@code uffff}, then it is + * represented by three bytes, to be written + * in the order shown:

{@code
+	 * (byte)(0xe0 | (0x0f & (c >> 12)))
+	 * (byte)(0x80 | (0x3f & (c >>  6)))
+	 * (byte)(0x80 | (0x3f & c))
+	 * }

First, + * the total number of bytes needed to represent + * all the characters of {@code s} is + * calculated. If this number is larger than + * {@code 65535}, then a {@code UTFDataFormatException} + * is thrown. Otherwise, this length is written + * to the output stream in exactly the manner + * of the {@code writeShort} method; + * after this, the one-, two-, or three-byte + * representation of each character in the + * string {@code s} is written.

The + * bytes written by this method may be read + * by the {@code readUTF} method of interface + * {@code DataInput}, which will then + * return a {@code String} equal to {@code s}. + * + * @param s the string value to be written. + */ + void writeUTF(String s); +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeDataOutputStream.java b/src/main/java/org/warp/commonutils/stream/SafeDataOutputStream.java new file mode 100644 index 0000000..32cc8d5 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeDataOutputStream.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.warp.commonutils.stream; + +import java.io.DataOutputStream; + +/** + * A data output stream lets an application write primitive Java data + * types to an output stream in a portable way. An application can + * then use a data input stream to read the data back in. + * + * @author unascribed + * @see java.io.DataInputStream + * @since 1.0 + */ +public class SafeDataOutputStream extends SafeFilterOutputStream implements SafeDataOutput { + /** + * The number of bytes written to the data output stream so far. + * If this counter overflows, it will be wrapped to Integer.MAX_VALUE. + */ + protected int written; + + /** + * bytearr is initialized on demand by writeUTF + */ + private byte[] bytearr = null; + + /** + * Creates a new data output stream to write data to the specified + * underlying output stream. The counter {@code written} is + * set to zero. + * + * @param out the underlying output stream, to be saved for later + * use. + * @see SafeFilterOutputStream#out + */ + public SafeDataOutputStream(SafeOutputStream out) { + super(out); + } + + /** + * Increases the written counter by the specified value + * until it reaches Integer.MAX_VALUE. + */ + private void incCount(int value) { + int temp = written + value; + if (temp < 0) { + temp = Integer.MAX_VALUE; + } + written = temp; + } + + /** + * Writes the specified byte (the low eight bits of the argument + * {@code b}) to the underlying output stream. If no exception + * is thrown, the counter {@code written} is incremented by + * {@code 1}. + *

+ * Implements the {@code write} method of {@code OutputStream}. + * + * @param b the {@code byte} to be written. + * @see SafeFilterOutputStream#out + */ + public synchronized void write(int b) { + out.write(b); + incCount(1); + } + + /** + * Writes {@code len} bytes from the specified byte array + * starting at offset {@code off} to the underlying output stream. + * If no exception is thrown, the counter {@code written} is + * incremented by {@code len}. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @see SafeFilterOutputStream#out + */ + public synchronized void write(byte b[], int off, int len) + { + out.write(b, off, len); + incCount(len); + } + + /** + * Flushes this data output stream. This forces any buffered output + * bytes to be written out to the stream. + *

+ * The {@code flush} method of {@code SafeDataOutputStream} + * calls the {@code flush} method of its underlying output stream. + * + * @see SafeFilterOutputStream#out + * @see java.io.OutputStream#flush() + */ + public void flush() { + out.flush(); + } + + /** + * Writes a {@code boolean} to the underlying output stream as + * a 1-byte value. The value {@code true} is written out as the + * value {@code (byte)1}; the value {@code false} is + * written out as the value {@code (byte)0}. If no exception is + * thrown, the counter {@code written} is incremented by + * {@code 1}. + * + * @param v a {@code boolean} value to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeBoolean(boolean v) { + out.write(v ? 1 : 0); + incCount(1); + } + + /** + * Writes out a {@code byte} to the underlying output stream as + * a 1-byte value. If no exception is thrown, the counter + * {@code written} is incremented by {@code 1}. + * + * @param v a {@code byte} value to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeByte(int v) { + out.write(v); + incCount(1); + } + + /** + * Writes a {@code short} to the underlying output stream as two + * bytes, high byte first. If no exception is thrown, the counter + * {@code written} is incremented by {@code 2}. + * + * @param v a {@code short} to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeShort(int v) { + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + incCount(2); + } + + /** + * Writes a {@code char} to the underlying output stream as a + * 2-byte value, high byte first. If no exception is thrown, the + * counter {@code written} is incremented by {@code 2}. + * + * @param v a {@code char} value to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeChar(int v) { + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + incCount(2); + } + + /** + * Writes an {@code int} to the underlying output stream as four + * bytes, high byte first. If no exception is thrown, the counter + * {@code written} is incremented by {@code 4}. + * + * @param v an {@code int} to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeInt(int v) { + out.write((v >>> 24) & 0xFF); + out.write((v >>> 16) & 0xFF); + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + incCount(4); + } + + private byte writeBuffer[] = new byte[8]; + + /** + * Writes a {@code long} to the underlying output stream as eight + * bytes, high byte first. In no exception is thrown, the counter + * {@code written} is incremented by {@code 8}. + * + * @param v a {@code long} to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeLong(long v) { + writeBuffer[0] = (byte)(v >>> 56); + writeBuffer[1] = (byte)(v >>> 48); + writeBuffer[2] = (byte)(v >>> 40); + writeBuffer[3] = (byte)(v >>> 32); + writeBuffer[4] = (byte)(v >>> 24); + writeBuffer[5] = (byte)(v >>> 16); + writeBuffer[6] = (byte)(v >>> 8); + writeBuffer[7] = (byte)(v >>> 0); + out.write(writeBuffer, 0, 8); + incCount(8); + } + + /** + * Converts the float argument to an {@code int} using the + * {@code floatToIntBits} method in class {@code Float}, + * and then writes that {@code int} value to the underlying + * output stream as a 4-byte quantity, high byte first. If no + * exception is thrown, the counter {@code written} is + * incremented by {@code 4}. + * + * @param v a {@code float} value to be written. + * @see SafeFilterOutputStream#out + * @see java.lang.Float#floatToIntBits(float) + */ + public final void writeFloat(float v) { + writeInt(Float.floatToIntBits(v)); + } + + /** + * Converts the double argument to a {@code long} using the + * {@code doubleToLongBits} method in class {@code Double}, + * and then writes that {@code long} value to the underlying + * output stream as an 8-byte quantity, high byte first. If no + * exception is thrown, the counter {@code written} is + * incremented by {@code 8}. + * + * @param v a {@code double} value to be written. + * @see SafeFilterOutputStream#out + * @see java.lang.Double#doubleToLongBits(double) + */ + public final void writeDouble(double v) { + writeLong(Double.doubleToLongBits(v)); + } + + /** + * Writes out the string to the underlying output stream as a + * sequence of bytes. Each character in the string is written out, in + * sequence, by discarding its high eight bits. If no exception is + * thrown, the counter {@code written} is incremented by the + * length of {@code s}. + * + * @param s a string of bytes to be written. + * @see SafeFilterOutputStream#out + */ + public final void writeBytes(String s) { + int len = s.length(); + for (int i = 0 ; i < len ; i++) { + out.write((byte)s.charAt(i)); + } + incCount(len); + } + + /** + * Writes a string to the underlying output stream as a sequence of + * characters. Each character is written to the data output stream as + * if by the {@code writeChar} method. If no exception is + * thrown, the counter {@code written} is incremented by twice + * the length of {@code s}. + * + * @param s a {@code String} value to be written. + * @see SafeDataOutputStream#writeChar(int) + * @see SafeFilterOutputStream#out + */ + public final void writeChars(String s) { + int len = s.length(); + for (int i = 0 ; i < len ; i++) { + int v = s.charAt(i); + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + } + incCount(len * 2); + } + + /** + * Writes a string to the underlying output stream using + * modified UTF-8 + * encoding in a machine-independent manner. + *

+ * First, two bytes are written to the output stream as if by the + * {@code writeShort} method giving the number of bytes to + * follow. This value is the number of bytes actually written out, + * not the length of the string. Following the length, each character + * of the string is output, in sequence, using the modified UTF-8 encoding + * for the character. If no exception is thrown, the counter + * {@code written} is incremented by the total number of + * bytes written to the output stream. This will be at least two + * plus the length of {@code str}, and at most two plus + * thrice the length of {@code str}. + * + * @param str a string to be written. + * @see #writeChars(String) + */ + public final void writeUTF(String str) { + writeUTF(str, this); + } + + /** + * Writes a string to the specified DataOutput using + * modified UTF-8 + * encoding in a machine-independent manner. + *

+ * First, two bytes are written to out as if by the {@code writeShort} + * method giving the number of bytes to follow. This value is the number of + * bytes actually written out, not the length of the string. Following the + * length, each character of the string is output, in sequence, using the + * modified UTF-8 encoding for the character. If no exception is thrown, the + * counter {@code written} is incremented by the total number of + * bytes written to the output stream. This will be at least two + * plus the length of {@code str}, and at most two plus + * thrice the length of {@code str}. + * + * @param str a string to be written. + * @param out destination to write to + * @return The number of bytes written out. + */ + static int writeUTF(String str, SafeDataOutput out) { + final int strlen = str.length(); + int utflen = strlen; // optimized for ASCII + + for (int i = 0; i < strlen; i++) { + int c = str.charAt(i); + if (c >= 0x80 || c == 0) + utflen += (c >= 0x800) ? 2 : 1; + } + + if (utflen > 65535 || /* overflow */ utflen < strlen) + throw new IllegalArgumentException(tooLongMsg(str, utflen)); + + final byte[] bytearr; + if (out instanceof SafeDataOutputStream) { + SafeDataOutputStream dos = (SafeDataOutputStream)out; + if (dos.bytearr == null || (dos.bytearr.length < (utflen + 2))) + dos.bytearr = new byte[(utflen*2) + 2]; + bytearr = dos.bytearr; + } else { + bytearr = new byte[utflen + 2]; + } + + int count = 0; + bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF); + bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); + + int i = 0; + for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII + int c = str.charAt(i); + if (c >= 0x80 || c == 0) break; + bytearr[count++] = (byte) c; + } + + for (; i < strlen; i++) { + int c = str.charAt(i); + if (c < 0x80 && c != 0) { + bytearr[count++] = (byte) c; + } else if (c >= 0x800) { + bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } else { + bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + } + out.write(bytearr, 0, utflen + 2); + return utflen + 2; + } + + private static String tooLongMsg(String s, int bits32) { + int slen = s.length(); + String head = s.substring(0, 8); + String tail = s.substring(slen - 8, slen); + // handle int overflow with max 3x expansion + long actualLength = (long)slen + Integer.toUnsignedLong(bits32 - slen); + return "encoded string (" + head + "..." + tail + ") too long: " + + actualLength + " bytes"; + } + + /** + * Returns the current value of the counter {@code written}, + * the number of bytes written to this data output stream so far. + * If the counter overflows, it will be wrapped to Integer.MAX_VALUE. + * + * @return the value of the {@code written} field. + * @see SafeDataOutputStream#written + */ + public final int size() { + return written; + } + + public DataOutputStream asDataOutputStream() { + return new DataOutputStream(this.out); + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeFilterInputStream.java b/src/main/java/org/warp/commonutils/stream/SafeFilterInputStream.java new file mode 100644 index 0000000..28be7e9 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeFilterInputStream.java @@ -0,0 +1,210 @@ +package org.warp.commonutils.stream; + +/** + * A {@code FilterInputStream} contains + * some other input stream, which it uses as + * its basic source of data, possibly transforming + * the data along the way or providing additional + * functionality. The class {@code FilterInputStream} + * itself simply overrides all methods of + * {@code InputStream} with versions that + * pass all requests to the contained input + * stream. Subclasses of {@code FilterInputStream} + * may further override some of these methods + * and may also provide additional methods + * and fields. + * + * @author Jonathan Payne + * @since 1.0 + */ +public class SafeFilterInputStream extends SafeInputStream { + /** + * The input stream to be filtered. + */ + protected volatile SafeInputStream in; + + /** + * Creates a {@code FilterInputStream} + * by assigning the argument {@code in} + * to the field {@code this.in} so as + * to remember it for later use. + * + * @param in the underlying input stream, or {@code null} if + * this instance is to be created without an underlying stream. + */ + protected SafeFilterInputStream(SafeInputStream in) { + this.in = in; + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an {@code int} in the range + * {@code 0} to {@code 255}. If no byte is available + * because the end of the stream has been reached, the value + * {@code -1} is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * This method + * simply performs {@code in.read()} and returns the result. + * + * @return the next byte of data, or {@code -1} if the end of the + * stream is reached. + * @see SafeFilterInputStream#in + */ + public int read() { + return in.read(); + } + + /** + * Reads up to {@code b.length} bytes of data from this + * input stream into an array of bytes. This method blocks until some + * input is available. + *

+ * This method simply performs the call + * {@code read(b, 0, b.length)} and returns + * the result. It is important that it does + * not do {@code in.read(b)} instead; + * certain subclasses of {@code FilterInputStream} + * depend on the implementation strategy actually + * used. + * + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of + * the stream has been reached. + * @see SafeFilterInputStream#read(byte[], int, int) + */ + public int read(byte b[]) { + return read(b, 0, b.length); + } + + /** + * Reads up to {@code len} bytes of data from this input stream + * into an array of bytes. If {@code len} is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and {@code 0} is returned. + *

+ * This method simply performs {@code in.read(b, off, len)} + * and returns the result. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of + * the stream has been reached. + * @throws NullPointerException If {@code b} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + * @see SafeFilterInputStream#in + */ + public int read(byte b[], int off, int len) { + return in.read(b, off, len); + } + + /** + * Skips over and discards {@code n} bytes of data from the + * input stream. The {@code skip} method may, for a variety of + * reasons, end up skipping over some smaller number of bytes, + * possibly {@code 0}. The actual number of bytes skipped is + * returned. + *

+ * This method simply performs {@code in.skip(n)}. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + */ + public long skip(long n) { + return in.skip(n); + } + + /** + * Returns an estimate of 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 another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + *

+ * This method returns the result of {@link #in in}.available(). + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking. + */ + public int available() { + return in.available(); + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * This + * method simply performs {@code in.close()}. + * + * @see SafeFilterInputStream#in + */ + public void close() { + in.close(); + } + + /** + * Marks the current position in this input stream. A subsequent + * call to the {@code reset} method repositions this stream at + * the last marked position so that subsequent reads re-read the same bytes. + *

+ * The {@code readlimit} argument tells this input stream to + * allow that many bytes to be read before the mark position gets + * invalidated. + *

+ * This method simply performs {@code in.mark(readlimit)}. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see SafeFilterInputStream#in + * @see SafeFilterInputStream#reset() + */ + public synchronized void mark(int readlimit) { + in.mark(readlimit); + } + + /** + * Repositions this stream to the position at the time the + * {@code mark} method was last called on this input stream. + *

+ * This method + * simply performs {@code in.reset()}. + *

+ * Stream marks are intended to be used in + * situations where you need to read ahead a little to see what's in + * the stream. Often this is most easily done by invoking some + * general parser. If the stream is of the type handled by the + * parse, it just chugs along happily. If the stream is not of + * that type, the parser should toss an exception when it fails. + * If this happens within readlimit bytes, it allows the outer + * code to reset the stream and try another parser. + * + * @see SafeFilterInputStream#in + * @see SafeFilterInputStream#mark(int) + */ + public synchronized void reset() { + in.reset(); + } + + /** + * Tests if this input stream supports the {@code mark} + * and {@code reset} methods. + * This method + * simply performs {@code in.markSupported()}. + * + * @return {@code true} if this stream type supports the + * {@code mark} and {@code reset} method; + * {@code false} otherwise. + * @see SafeFilterInputStream#in + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + public boolean markSupported() { + return in.markSupported(); + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeFilterOutputStream.java b/src/main/java/org/warp/commonutils/stream/SafeFilterOutputStream.java new file mode 100644 index 0000000..f4fd0e5 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeFilterOutputStream.java @@ -0,0 +1,180 @@ +package org.warp.commonutils.stream; + + +/** + * This class is the superclass of all classes that filter output + * streams. These streams sit on top of an already existing output + * stream (the underlying output stream) which it uses as its + * basic sink of data, but possibly transforming the data along the + * way or providing additional functionality. + *

+ * The class {@code FilterOutputStream} itself simply overrides + * all methods of {@code SafeOutputStream} with versions that pass + * all requests to the underlying output stream. Subclasses of + * {@code FilterOutputStream} may further override some of these + * methods as well as provide additional methods and fields. + * + * @author Jonathan Payne + * @since 1.0 + */ +public class SafeFilterOutputStream extends SafeOutputStream { + /** + * The underlying output stream to be filtered. + */ + protected SafeOutputStream out; + + /** + * Whether the stream is closed; implicitly initialized to false. + */ + private volatile boolean closed; + + /** + * Object used to prevent a race on the 'closed' instance variable. + */ + private final Object closeLock = new Object(); + + /** + * Creates an output stream filter built on top of the specified + * underlying output stream. + * + * @param out the underlying output stream to be assigned to + * the field {@code this.out} for later use, or + * {@code null} if this instance is to be + * created without an underlying stream. + */ + public SafeFilterOutputStream(SafeOutputStream out) { + this.out = out; + } + + /** + * Writes the specified {@code byte} to this output stream. + *

+ * The {@code write} method of {@code FilterOutputStream} + * calls the {@code write} method of its underlying output stream, + * that is, it performs {@code out.write(b)}. + *

+ * Implements the abstract {@code write} method of {@code SafeOutputStream}. + * + * @param b the {@code byte}. + */ + @Override + public void write(int b) { + out.write(b); + } + + /** + * Writes {@code b.length} bytes to this output stream. + *

+ * The {@code write} method of {@code FilterOutputStream} + * calls its {@code write} method of three arguments with the + * arguments {@code b}, {@code 0}, and + * {@code b.length}. + *

+ * Note that this method does not call the one-argument + * {@code write} method of its underlying output stream with + * the single argument {@code b}. + * + * @param b the data to be written. + * @see SafeFilterOutputStream#write(byte[], int, int) + */ + @Override + public void write(byte b[]) { + write(b, 0, b.length); + } + + /** + * Writes {@code len} bytes from the specified + * {@code byte} array starting at offset {@code off} to + * this output stream. + *

+ * The {@code write} method of {@code FilterOutputStream} + * calls the {@code write} method of one argument on each + * {@code byte} to output. + *

+ * Note that this method does not call the {@code write} method + * of its underlying output stream with the same arguments. Subclasses + * of {@code FilterOutputStream} should provide a more efficient + * implementation of this method. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @see SafeFilterOutputStream#write(int) + */ + @Override + public void write(byte b[], int off, int len) { + if ((off | len | (b.length - (len + off)) | (off + len)) < 0) + throw new IndexOutOfBoundsException(); + + for (int i = 0 ; i < len ; i++) { + write(b[off + i]); + } + } + + /** + * Flushes this output stream and forces any buffered output bytes + * to be written out to the stream. + *

+ * The {@code flush} method of {@code FilterOutputStream} + * calls the {@code flush} method of its underlying output stream. + * + * @see SafeFilterOutputStream#out + */ + @Override + public void flush() { + out.flush(); + } + + /** + * Closes this output stream and releases any system resources + * associated with the stream. + *

+ * When not already closed, the {@code close} method of {@code + * FilterOutputStream} calls its {@code flush} method, and then + * calls the {@code close} method of its underlying output stream. + * + * @see SafeFilterOutputStream#flush() + * @see SafeFilterOutputStream#out + */ + @Override + public void close() { + if (closed) { + return; + } + synchronized (closeLock) { + if (closed) { + return; + } + closed = true; + } + + Throwable flushException = null; + try { + flush(); + } catch (Throwable e) { + flushException = e; + throw e; + } finally { + if (flushException == null) { + out.close(); + } else { + try { + out.close(); + } catch (Throwable closeException) { + // evaluate possible precedence of flushException over closeException + if ((flushException instanceof ThreadDeath) && + !(closeException instanceof ThreadDeath)) { + flushException.addSuppressed(closeException); + throw (ThreadDeath) flushException; + } + + if (flushException != closeException) { + closeException.addSuppressed(flushException); + } + + throw closeException; + } + } + } + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeInputStream.java b/src/main/java/org/warp/commonutils/stream/SafeInputStream.java new file mode 100644 index 0000000..db60727 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeInputStream.java @@ -0,0 +1,201 @@ +package org.warp.commonutils.stream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public abstract class SafeInputStream extends InputStream { + + // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to + // use when skipping. + private static final int MAX_SKIP_BUFFER_SIZE = 2048; + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + @Override + public abstract int read(); + + public int read(byte b[]) { + return read(b, 0, b.length); + } + + public int read(byte b[], int off, int len) { + Objects.checkFromIndexSize(off, len, b.length); + if (len == 0) { + return 0; + } + + int c = read(); + if (c == -1) { + return -1; + } + b[off] = (byte)c; + + int i = 1; + for (; i < len ; i++) { + c = read(); + if (c == -1) { + break; + } + b[off + i] = (byte)c; + } + return i; + } + + public byte[] readAllBytes() { + return readNBytes(Integer.MAX_VALUE); + } + + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + + public byte[] readNBytes(int len) { + if (len < 0) { + throw new IllegalArgumentException("len < 0"); + } + + List bufs = null; + byte[] result = null; + int total = 0; + int remaining = len; + int n; + do { + byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)]; + int nread = 0; + + // read to EOF which may read more or less than buffer size + while ((n = read(buf, nread, + Math.min(buf.length - nread, remaining))) > 0) { + nread += n; + remaining -= n; + } + + if (nread > 0) { + if (MAX_BUFFER_SIZE - total < nread) { + throw new OutOfMemoryError("Required array size too large"); + } + total += nread; + if (result == null) { + result = buf; + } else { + if (bufs == null) { + bufs = new ArrayList<>(); + bufs.add(result); + } + bufs.add(buf); + } + } + // if the last call to read returned -1 or the number of bytes + // requested have been read then break + } while (n >= 0 && remaining > 0); + + if (bufs == null) { + if (result == null) { + return new byte[0]; + } + return result.length == total ? + result : Arrays.copyOf(result, total); + } + + result = new byte[total]; + int offset = 0; + remaining = total; + for (byte[] b : bufs) { + int count = Math.min(b.length, remaining); + System.arraycopy(b, 0, result, offset, count); + offset += count; + remaining -= count; + } + + return result; + } + + public int readNBytes(byte[] b, int off, int len) { + Objects.checkFromIndexSize(off, len, b.length); + + int n = 0; + while (n < len) { + int count = read(b, off + n, len - n); + if (count < 0) + break; + n += count; + } + return n; + } + + public long skip(long n) { + long remaining = n; + int nr; + + if (n <= 0) { + return 0; + } + + int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); + byte[] skipBuffer = new byte[size]; + while (remaining > 0) { + nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); + if (nr < 0) { + break; + } + remaining -= nr; + } + + return n - remaining; + } + + public void skipNBytes(long n) { + if (n > 0) { + long ns = skip(n); + if (ns >= 0 && ns < n) { // skipped too few bytes + // adjust number to skip + n -= ns; + // read until requested number skipped or EOS reached + while (n > 0 && read() != -1) { + n--; + } + // if not enough skipped, then EOFE + if (n != 0) { + throw new IndexOutOfBoundsException(); + } + } else if (ns != n) { // skipped negative or too many bytes + throw new IllegalArgumentException("Unable to skip exactly"); + } + } + } + + public int available() { + return 0; + } + + public void close() {} + + public synchronized void mark(int readlimit) {} + + public synchronized void reset() { + throw new UnsupportedOperationException("mark/reset not supported"); + } + + public boolean markSupported() { + return false; + } + + public long transferTo(OutputStream out) { + Objects.requireNonNull(out, "out"); + long transferred = 0; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int read; + while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) { + try { + out.write(buffer, 0, read); + } catch (IOException e) { + throw new IllegalStateException(e); + } + transferred += read; + } + return transferred; + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeMeasurableInputStream.java b/src/main/java/org/warp/commonutils/stream/SafeMeasurableInputStream.java new file mode 100644 index 0000000..547f847 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeMeasurableInputStream.java @@ -0,0 +1,28 @@ +package org.warp.commonutils.stream; + +/* + * Copyright (C) 2005-2020 Sebastiano Vigna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import java.io.InputStream; + +/** An {@link InputStream} that implements also the {@link SafeMeasurableStream} interface. + * + * @since 5.0.4 + */ + +public abstract class SafeMeasurableInputStream extends SafeInputStream implements SafeMeasurableStream { +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeMeasurableOutputStream.java b/src/main/java/org/warp/commonutils/stream/SafeMeasurableOutputStream.java new file mode 100644 index 0000000..fb67530 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeMeasurableOutputStream.java @@ -0,0 +1,27 @@ +package org.warp.commonutils.stream; + +/* + * Copyright (C) 2005-2020 Sebastiano Vigna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.OutputStream; + +/** An {@link OutputStream} that implements also the {@link SafeMeasurableStream} interface. + * + * @since 6.0.0 + */ + +public abstract class SafeMeasurableOutputStream extends SafeOutputStream implements SafeMeasurableStream { +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeMeasurableStream.java b/src/main/java/org/warp/commonutils/stream/SafeMeasurableStream.java new file mode 100644 index 0000000..3e8ecc5 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeMeasurableStream.java @@ -0,0 +1,49 @@ +package org.warp.commonutils.stream; + +/* + * Copyright (C) 2005-2020 Sebastiano Vigna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** An stream that provides eager access to its length, + * and keeps track of the current position (e.g., the number of bytes read so far, or the current + * position of the file pointer). + * + *

This class has two methods, both specified as optional. This apparently bizarre + * behaviour is necessary because of wrapper classes which use reflection + * to support those methods (see, e.g., {@link MeasurableInputStream}, {@link FastBufferedInputStream} and {@link FastBufferedOutputStream}). + * + * @since 6.0.0 + */ + +public interface SafeMeasurableStream { + + /** Returns the overall length of this stream (optional operation). In most cases, this will require the + * stream to perform some extra action, possibly changing the state of the input stream itself (typically, reading + * all the bytes up to the end, or flushing on output stream). + * Implementing classes should always document what state will the input stream be in + * after calling this method, and which kind of exception could be thrown. + */ + long length(); + + /** Returns the current position in this stream (optional operation). + * + *

Usually, the position is just the number of bytes read or written + * since the stream was opened, but in the case of a + * {@link it.unimi.dsi.fastutil.io.RepositionableStream} it + * represent the current position. + */ + long position(); +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeOutputStream.java b/src/main/java/org/warp/commonutils/stream/SafeOutputStream.java new file mode 100644 index 0000000..f0190d1 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeOutputStream.java @@ -0,0 +1,168 @@ +package org.warp.commonutils.stream; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.OutputStream; +import java.util.Objects; + +/** + * This abstract class is the superclass of all classes representing + * an output stream of bytes. An output stream accepts output bytes + * and sends them to some sink. + *

+ * Applications that need to define a subclass of + * {@code OutputStream} must always provide at least a method + * that writes one byte of output. + * + * @author Arthur van Hoff + * @see java.io.BufferedOutputStream + * @see java.io.ByteArrayOutputStream + * @see java.io.DataOutputStream + * @see java.io.FilterOutputStream + * @see java.io.InputStream + * @see java.io.OutputStream#write(int) + * @since 1.0 + */ +public abstract class SafeOutputStream extends OutputStream implements Closeable, Flushable { + /** + * Constructor for subclasses to call. + */ + public SafeOutputStream() {} + + /** + * Returns a new {@code OutputStream} which discards all bytes. The + * returned stream is initially open. The stream is closed by calling + * the {@code close()} method. Subsequent calls to {@code close()} have + * no effect. + * + *

While the stream is open, the {@code write(int)}, {@code + * write(byte[])}, and {@code write(byte[], int, int)} methods do nothing. + * After the stream has been closed, these methods all throw {@code + * IOException}. + * + *

The {@code flush()} method does nothing. + * + * @return an {@code OutputStream} which discards all bytes + * + * @since 11 + */ + public static java.io.OutputStream nullOutputStream() { + return new java.io.OutputStream() { + private volatile boolean closed; + + private void ensureOpen() { + if (closed) { + throw new IllegalStateException("Stream closed"); + } + } + + @Override + public void write(int b) { + ensureOpen(); + } + + @Override + public void write(byte b[], int off, int len) { + Objects.checkFromIndexSize(off, len, b.length); + ensureOpen(); + } + + @Override + public void close() { + closed = true; + } + }; + } + + /** + * Writes the specified byte to this output stream. The general + * contract for {@code write} is that one byte is written + * to the output stream. The byte to be written is the eight + * low-order bits of the argument {@code b}. The 24 + * high-order bits of {@code b} are ignored. + *

+ * Subclasses of {@code OutputStream} must provide an + * implementation for this method. + * + * @param b the {@code byte}. + */ + public abstract void write(int b); + + /** + * Writes {@code b.length} bytes from the specified byte array + * to this output stream. The general contract for {@code write(b)} + * is that it should have exactly the same effect as the call + * {@code write(b, 0, b.length)}. + * + * @param b the data. + * @see java.io.OutputStream#write(byte[], int, int) + */ + public void write(byte b[]) { + write(b, 0, b.length); + } + + /** + * Writes {@code len} bytes from the specified byte array + * starting at offset {@code off} to this output stream. + * The general contract for {@code write(b, off, len)} is that + * some of the bytes in the array {@code b} are written to the + * output stream in order; element {@code b[off]} is the first + * byte written and {@code b[off+len-1]} is the last byte written + * by this operation. + *

+ * The {@code write} method of {@code OutputStream} calls + * the write method of one argument on each of the bytes to be + * written out. Subclasses are encouraged to override this method and + * provide a more efficient implementation. + *

+ * If {@code b} is {@code null}, a + * {@code NullPointerException} is thrown. + *

+ * If {@code off} is negative, or {@code len} is negative, or + * {@code off+len} is greater than the length of the array + * {@code b}, then an {@code IndexOutOfBoundsException} is thrown. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + */ + public void write(byte b[], int off, int len) { + Objects.checkFromIndexSize(off, len, b.length); + // len == 0 condition implicitly handled by loop bounds + for (int i = 0 ; i < len ; i++) { + write(b[off + i]); + } + } + + /** + * Flushes this output stream and forces any buffered output bytes + * to be written out. The general contract of {@code flush} is + * that calling it is an indication that, if any bytes previously + * written have been buffered by the implementation of the output + * stream, such bytes should immediately be written to their + * intended destination. + *

+ * If the intended destination of this stream is an abstraction provided by + * the underlying operating system, for example a file, then flushing the + * stream guarantees only that bytes previously written to the stream are + * passed to the operating system for writing; it does not guarantee that + * they are actually written to a physical device such as a disk drive. + *

+ * The {@code flush} method of {@code OutputStream} does nothing. + * + */ + public void flush() { + } + + /** + * Closes this output stream and releases any system resources + * associated with this stream. The general contract of {@code close} + * is that it closes the output stream. A closed stream cannot perform + * output operations and cannot be reopened. + *

+ * The {@code close} method of {@code OutputStream} does nothing. + * + */ + public void close() { + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafePushbackInputStream.java b/src/main/java/org/warp/commonutils/stream/SafePushbackInputStream.java new file mode 100644 index 0000000..04420d9 --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafePushbackInputStream.java @@ -0,0 +1,332 @@ +package org.warp.commonutils.stream; + +/** + * A {@code PushbackInputStream} adds + * functionality to another input stream, namely + * the ability to "push back" or "unread" bytes, + * by storing pushed-back bytes in an internal buffer. + * This is useful in situations where + * it is convenient for a fragment of code + * to read an indefinite number of data bytes + * that are delimited by a particular byte + * value; after reading the terminating byte, + * the code fragment can "unread" it, so that + * the next read operation on the input stream + * will reread the byte that was pushed back. + * For example, bytes representing the characters + * constituting an identifier might be terminated + * by a byte representing an operator character; + * a method whose job is to read just an identifier + * can read until it sees the operator and + * then push the operator back to be re-read. + * + * @author David Connelly + * @author Jonathan Payne + * @since 1.0 + */ +public class SafePushbackInputStream extends SafeFilterInputStream { + /** + * The pushback buffer. + * @since 1.1 + */ + protected byte[] buf; + + /** + * The position within the pushback buffer from which the next byte will + * be read. When the buffer is empty, {@code pos} is equal to + * {@code buf.length}; when the buffer is full, {@code pos} is + * equal to zero. + * + * @since 1.1 + */ + protected int pos; + + /** + * Check to make sure that this stream has not been closed + */ + private void ensureOpen() { + if (in == null) + throw new IllegalStateException("Stream closed"); + } + + /** + * Creates a {@code PushbackInputStream} + * with a pushback buffer of the specified {@code size}, + * and saves its argument, the input stream + * {@code in}, for later use. Initially, + * the pushback buffer is empty. + * + * @param in the input stream from which bytes will be read. + * @param size the size of the pushback buffer. + * @throws IllegalArgumentException if {@code size <= 0} + * @since 1.1 + */ + public SafePushbackInputStream(SafeInputStream in, int size) { + super(in); + if (size <= 0) { + throw new IllegalArgumentException("size <= 0"); + } + this.buf = new byte[size]; + this.pos = size; + } + + /** + * Creates a {@code PushbackInputStream} + * with a 1-byte pushback buffer, and saves its argument, the input stream + * {@code in}, for later use. Initially, + * the pushback buffer is empty. + * + * @param in the input stream from which bytes will be read. + */ + public SafePushbackInputStream(SafeInputStream in) { + this(in, 1); + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an {@code int} in the range + * {@code 0} to {@code 255}. If no byte is available + * because the end of the stream has been reached, the value + * {@code -1} is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + * + *

This method returns the most recently pushed-back byte, if there is + * one, and otherwise calls the {@code read} method of its underlying + * input stream and returns whatever value that method returns. + * + * @return the next byte of data, or {@code -1} if the end of the + * stream has been reached. + * or an I/O error occurs. + * @see java.io.InputStream#read() + */ + public int read() { + ensureOpen(); + if (pos < buf.length) { + return buf[pos++] & 0xff; + } + return super.read(); + } + + /** + * Reads up to {@code len} bytes of data from this input stream into + * an array of bytes. This method first reads any pushed-back bytes; after + * that, if fewer than {@code len} bytes have been read then it + * reads from the underlying input stream. If {@code len} is not zero, the method + * blocks until at least 1 byte of input is available; otherwise, no + * bytes are read and {@code 0} is returned. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array {@code b} + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of + * the stream has been reached. + * @throws NullPointerException If {@code b} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + * or an I/O error occurs. + * @see java.io.InputStream#read(byte[], int, int) + */ + public int read(byte[] b, int off, int len) { + ensureOpen(); + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int avail = buf.length - pos; + if (avail > 0) { + if (len < avail) { + avail = len; + } + System.arraycopy(buf, pos, b, off, avail); + pos += avail; + off += avail; + len -= avail; + } + if (len > 0) { + len = super.read(b, off, len); + if (len == -1) { + return avail == 0 ? -1 : avail; + } + return avail + len; + } + return avail; + } + + /** + * Pushes back a byte by copying it to the front of the pushback buffer. + * After this method returns, the next byte to be read will have the value + * {@code (byte)b}. + * + * @param b the {@code int} value whose low-order + * byte is to be pushed back. + */ + public void unread(int b) { + ensureOpen(); + if (pos == 0) { + throw new IllegalStateException("Push back buffer is full"); + } + buf[--pos] = (byte)b; + } + + /** + * Pushes back a portion of an array of bytes by copying it to the front + * of the pushback buffer. After this method returns, the next byte to be + * read will have the value {@code b[off]}, the byte after that will + * have the value {@code b[off+1]}, and so forth. + * + * @param b the byte array to push back. + * @param off the start offset of the data. + * @param len the number of bytes to push back. + * @throws NullPointerException If {@code b} is {@code null}. + * @since 1.1 + */ + public void unread(byte[] b, int off, int len) { + ensureOpen(); + if (len > pos) { + throw new IllegalStateException("Push back buffer is full"); + } + pos -= len; + System.arraycopy(b, off, buf, pos, len); + } + + /** + * Pushes back an array of bytes by copying it to the front of the + * pushback buffer. After this method returns, the next byte to be read + * will have the value {@code b[0]}, the byte after that will have the + * value {@code b[1]}, and so forth. + * + * @param b the byte array to push back + * @throws NullPointerException If {@code b} is {@code null}. + * @since 1.1 + */ + public void unread(byte[] b) { + unread(b, 0, b.length); + } + + /** + * Returns an estimate of the number of bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * invocation of a method for this input stream. The next invocation might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + * + *

The method returns the sum of the number of bytes that have been + * pushed back and the value returned by {@link + * SafeFilterInputStream#available available}. + * + * @return the number of bytes that can be read (or skipped over) from + * the input stream without blocking. + * @see SafeFilterInputStream#in + * @see java.io.InputStream#available() + */ + public int available() { + ensureOpen(); + int n = buf.length - pos; + int avail = super.available(); + return n > (Integer.MAX_VALUE - avail) + ? Integer.MAX_VALUE + : n + avail; + } + + /** + * Skips over and discards {@code n} bytes of data from this + * input stream. The {@code skip} method may, for a variety of + * reasons, end up skipping over some smaller number of bytes, + * possibly zero. If {@code n} is negative, no bytes are skipped. + * + *

The {@code skip} method of {@code PushbackInputStream} + * first skips over the bytes in the pushback buffer, if any. It then + * calls the {@code skip} method of the underlying input stream if + * more bytes need to be skipped. The actual number of bytes skipped + * is returned. + * + * @param n {@inheritDoc} + * @return {@inheritDoc} + * @see SafeFilterInputStream#in + * @see java.io.InputStream#skip(long n) + * @since 1.2 + */ + public long skip(long n) { + ensureOpen(); + if (n <= 0) { + return 0; + } + + long pskip = buf.length - pos; + if (pskip > 0) { + if (n < pskip) { + pskip = n; + } + pos += pskip; + n -= pskip; + } + if (n > 0) { + pskip += super.skip(n); + } + return pskip; + } + + /** + * Tests if this input stream supports the {@code mark} and + * {@code reset} methods, which it does not. + * + * @return {@code false}, since this class does not support the + * {@code mark} and {@code reset} methods. + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + public boolean markSupported() { + return false; + } + + /** + * Marks the current position in this input stream. + * + *

The {@code mark} method of {@code PushbackInputStream} + * does nothing. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.InputStream#reset() + */ + public synchronized void mark(int readlimit) { + } + + /** + * Repositions this stream to the position at the time the + * {@code mark} method was last called on this input stream. + * + *

The method {@code reset} for class + * {@code PushbackInputStream} does nothing except throw an + * {@code IOException}. + * + * @see java.io.InputStream#mark(int) + * @see java.io.IOException + */ + public synchronized void reset() { + throw new UnsupportedOperationException("mark/reset not supported"); + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * Once the stream has been closed, further read(), unread(), + * available(), reset(), or skip() invocations will throw an IOException. + * Closing a previously closed stream has no effect. + * + */ + public synchronized void close() { + if (in == null) + return; + in.close(); + in = null; + buf = null; + } +} diff --git a/src/main/java/org/warp/commonutils/stream/SafeRepositionableStream.java b/src/main/java/org/warp/commonutils/stream/SafeRepositionableStream.java new file mode 100644 index 0000000..e79f2df --- /dev/null +++ b/src/main/java/org/warp/commonutils/stream/SafeRepositionableStream.java @@ -0,0 +1,40 @@ +package org.warp.commonutils.stream; + +/* + * Copyright (C) 2005-2020 Sebastiano Vigna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** A basic interface specifying positioning methods for a byte stream. + * + * @author Sebastiano Vigna + * @since 4.4 + */ + +public interface SafeRepositionableStream { + + /** Sets the current stream position. + * + * @param newPosition the new stream position. + */ + void position(long newPosition); + + /** Returns the current stream position. + * + * @return the current stream position. + */ + long position(); + +}