net.freeutils.util.Streams Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util;
import java.io.*;
/**
* The Streams
class contains utility methods related to Streams and I/O.
*/
public class Streams {
/**
* Private constructor to avoid external instantiation.
*/
private Streams() {}
/**
* Reads bytes from a stream. The number of bytes read is no less
* than the given minimum and no more than the given maximum.
*
* @param in the input stream to read from
* @param b the byte array to read into
* @param off the offset within the byte array to start reading into
* @param min the minimum number of bytes to read
* @param max the maximum number of bytes to read
* @return the number of bytes read
* @throws IOException if an error occurs or there are less
* than the minimum number of bytes in the stream
*/
public static int read(InputStream in, byte[] b, int off, int min, int max) throws IOException {
int total = 0;
while (total < min) {
int read = in.read(b, off + total, max - total);
if (read < 0)
throw new IOException("Unexpected end of stream");
total += read;
}
return total;
}
/**
* Transfers data from an input stream to an output stream.
*
* @param in the input stream to read from
* @param out the output stream to write to
* @param maxLength maximum number of bytes to read;
* if negative, read until end of stream
* @param closeIn if true, the input stream is closed by this method
* @param closeOut if true, the output stream is closed by this method
* @return the number of bytes transferred
* @throws IOException if an error occurs
*/
public static long transfer(InputStream in, OutputStream out, long maxLength, boolean closeIn, boolean closeOut) throws IOException {
long len = maxLength;
byte[] buf = new byte[16384];
try {
while (len != 0) {
int count = len < 0 || buf.length < len ? buf.length : (int)len;
count = in.read(buf, 0, count);
if (count == -1)
break;
out.write(buf, 0, count);
len -= count;
}
} finally {
if (closeIn)
Streams.closeSilently(in);
if (closeOut)
Streams.closeSilently(out);
}
return maxLength - len;
}
/**
* Transfers the entire content of one stream into another stream.
*
* @param in the input stream to read from
* @param out the output stream to write to
* @param closeIn if true, the input stream is closed by this method
* @param closeOut if true, the output stream is closed by this method
* @throws IOException if an error occurs
*/
public static void transfer(InputStream in, OutputStream out, boolean closeIn, boolean closeOut) throws IOException {
transfer(in, out, -1, closeIn, closeOut);
}
/**
* Transfers the entire content of a stream into a file.
*
* @param in the input stream to read from
* @param out the output file to write to
* @param closeIn if true, the input stream is closed by this method
* @throws IOException if an error occurs
*/
public static void transfer(InputStream in, File out, boolean closeIn) throws IOException {
transfer(in, new FileOutputStream(out), -1, closeIn, true);
}
/**
* Transfers data from an input stream to an output stream.
*
* Neither stream is closed by this method.
*
* @param in the input stream to transfer from
* @param out the output stream to transfer to
* @param len the number of bytes to transfer. If negative, the
* entire contents of the input stream are transferred.
* @throws EOFException if the input stream ends before the
* requested number of bytes have been read
* @throws IOException if an IO error occurs
*/
public static void transfer(InputStream in, OutputStream out, long len)
throws IOException {
if (transfer(in, out, len, false, false) < len)
throw new EOFException("unexpected end of stream");
}
/**
* Closes all given Closeables in their natural order,
* silently ignoring nulls and thrown exceptions.
*
* @param closeables the Closeables to close
*/
public static void closeSilently(Closeable... closeables) {
for (Closeable c : closeables) {
try {
if (c != null)
c.close();
} catch (IOException ignore) {}
}
}
/**
* Reads the contents of a stream as a String in the given encoding.
* The stream is closed by this method.
*
* @param in the stream to read
* @param enc the encoding to use
* @return the file contents as a String in the given encoding
* @throws IOException if an error occurs
*/
public static String readString(InputStream in, String enc) throws IOException {
return Streams.readString(in, enc, -1);
}
/**
* Reads the contents of a stream as a String in the given encoding.
* The stream is closed by this method.
*
* @param in the stream to read
* @param enc the encoding to use
* @param maxLength maximum number of bytes to read;
* if negative, read until end of stream
* @return the file contents as a String in the given encoding
* @throws IOException if an error occurs
*/
public static String readString(InputStream in, String enc, int maxLength) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
Streams.transfer(in, out, maxLength, true, false);
return out.toString(enc);
}
/**
* Reads the contents of a stream as an array of text lines in the given encoding.
*
* @param in the stream from which the lines are read
* @param enc the encoding to use
* @param lenient if true, either an LF or a CRLF can end the line;
* if false, only a CRLF can end the line
* @return the lines read from the stream
* @throws IOException if an error occurs
*/
public static String[] readLines(InputStream in, String enc, boolean lenient) throws IOException {
return Strings.splitLines(readString(in, enc), lenient);
}
/**
* Reads the contents of a stream as a byte array.
* The stream is closed by this method.
*
* @param in the stream to read
* @return the file contents as a byte array
* @throws IOException if an error occurs
*/
public static byte[] readBytes(InputStream in) throws IOException {
return Streams.readBytes(in, -1);
}
/**
* Reads the contents of a stream as a byte array.
* The stream is closed by this method.
*
* @param in the stream to read
* @param maxLength maximum number of bytes to read;
* if negative, read until end of stream
* @return the file contents as a byte array
* @throws IOException if an error occurs
*/
public static byte[] readBytes(InputStream in, int maxLength) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
Streams.transfer(in, out, maxLength, true, false);
return out.toByteArray();
}
/**
* Reads the token starting at the current stream position and ending at
* the first occurrence of the given delimiter byte, in the given encoding.
*
* @param in the stream from which the token is read
* @param delim the byte value which marks the end of the token,
* or -1 if the token ends at the end of the stream
* @param enc a character-encoding name
* @param maxLength the maximum length (in bytes) to read
* @return the read token, excluding the delimiter
* @throws UnsupportedEncodingException if the encoding is not supported
* @throws EOFException if the stream end is reached before the delimiter
* is found (and it is not -1)
* @throws IOException if an IO error occurs, or the maximum length is
* reached before the token end is reached
*/
public static String readToken(InputStream in, int delim,
String enc, int maxLength) throws IOException {
// note: we avoid using a ByteArrayOutputStream here because it
// suffers the overhead of synchronization for each byte written
int buflen = maxLength < 512 ? maxLength : 512; // start with less
byte[] buf = new byte[buflen];
int count = 0;
int c;
while ((c = in.read()) != -1 && c != delim) {
if (count == buflen) { // expand buffer
if (count == maxLength)
throw new IOException("token too large (" + count + ")");
buflen = maxLength < 2 * buflen ? maxLength : 2 * buflen;
byte[] expanded = new byte[buflen];
System.arraycopy(buf, 0, expanded, 0, count);
buf = expanded;
}
buf[count++] = (byte)c;
}
if (c == -1 && delim != -1)
throw new EOFException("unexpected end of stream");
return new String(buf, 0, count, enc);
}
/**
* Reads the string starting at the current stream position and ending
* at the first occurrence of an end-of-line sequence, in the given
* encoding.
*
* @param in the stream from which the line is read
* @param enc the encoding to use
* @param lenient if true, either an LF or a CRLF can end the line;
* if false, only a CRLF can end the line
* @return the read string, excluding the end-of-line characters
* @throws EOFException if the stream end is reached before an
* end-of-line sequence is found
* @throws IOException if an IO error occurs, or the line is
* longer than 8192 bytes
* @see #readToken(InputStream, int, String, int)
*/
public static String readLine(InputStream in, String enc, boolean lenient) throws IOException {
String s = readToken(in, '\n', enc, 8192);
if (lenient) {
return s.length() > 0 && s.charAt(s.length() - 1) == '\r'
? s.substring(0, s.length() - 1) : s;
}
while (s.isEmpty() || s.charAt(s.length() - 1) != '\r') {
s += readToken(in, '\n', enc, 8192);
if (s.length() > 8192)
throw new IOException("token too large");
}
return s.substring(0, s.length() - 1);
}
/**
* Reads the ISO-8859-1 encoded string starting at the current stream
* position and ending at the first occurrence of an end-of-line
* sequence.
*
* @param in the stream from which the line is read
* @param lenient if true, either an LF or a CRLF can end the line;
* if false, only a CRLF can end the line
* @return the read string, excluding the end-of-line characters
* @throws EOFException if the stream end is reached before
* an end-of-line sequence is found
* @throws IOException if an IO error occurs, or the line is
* longer than 8192 bytes
* @see #readToken(InputStream, int, String, int)
*/
public static String readLine(InputStream in, boolean lenient) throws IOException {
return readLine(in, "ISO8859_1", lenient);
}
/**
* Reads the ISO-8859-1 encoded string starting at the current stream
* position and ending at the first occurrence of an end-of-line
* sequence (LF or CRLF).
*
* @param in the stream from which the line is read
* @return the read string, excluding the end-of-line characters
* @throws EOFException if the stream end is reached before
* an end-of-line sequence is found
* @throws IOException if an IO error occurs, or the line is
* longer than 8192 bytes
* @see #readToken(InputStream, int, String, int)
*/
public static String readLine(InputStream in) throws IOException {
return readLine(in, "ISO8859_1", true);
}
/**
* Reads the token starting at the current stream position and ending at
* the first occurrence of the given delimiter byte.
*
* @param in the stream from which the token is read
* @param delim the byte value which marks the end of the token,
* or -1 if the token ends at the end of the stream
* @param buf the buffer into which the line is written
* @param off the offset within buf where writing begins
* @return the number of bytes read (including the delimiter, if exists)
* @throws UnsupportedEncodingException if the encoding is not supported
* @throws EOFException if the stream end is reached before the delimiter
* is found (and it is not -1)
* @throws IOException if an IO error occurs
* @throws IndexOutOfBoundsException if the buffer is full before the
* token end is reached
*/
public static int readTokenBytes(InputStream in, int delim,
byte[] buf, int off) throws IOException {
int pos = off;
int c;
while ((c = in.read()) != -1) {
if (pos == buf.length)
throw new IndexOutOfBoundsException("token too large");
buf[pos++] = (byte)c;
if (c == delim)
break;
}
if (c == -1 && delim != -1)
throw new EOFException("unexpected end of stream");
return pos - off;
}
/**
* Reads bytes from a stream starting at the current stream
* position and ending at the first occurrence of the CRLF sequence.
*
* @param in the stream from which the line is read
* @param buf the buffer into which the line is written
* @return the number of bytes read (including the CRLF)
* @throws EOFException if the stream end is reached
* before a CRLF sequence is found
* @throws IOException if an IO error occurs
* @throws IndexOutOfBoundsException if the buffer is full before a
* CRLF is reached
* @see #readTokenBytes(InputStream, int, byte[], int)
*/
public static int readLineBytes(InputStream in, byte[] buf) throws IOException {
int pos = 0;
do {
pos += readTokenBytes(in, '\n', buf, pos);
} while (pos < 2 || buf[pos - 2] != '\r');
return pos;
}
/**
* Serializes the given object to a stream.
*
* @param out the stream to serialize to
* @param obj the object to serialize
* @param the object type
* @param close specifies whether the stream should be closed after writing
* @throws IOException if an error occurs
*/
public static void serialize(OutputStream out, T obj, boolean close) throws IOException {
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
bos = new BufferedOutputStream(out);
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
} finally {
Streams.closeSilently(oos, bos, close ? out : null);
}
}
/**
* Serializes the given object to its byte representation.
*
* The object can be reconstructed using the {@link #deserialize} method.
*
* @param obj the object to serialize
* @param the object type
* @return the serialized bytes
* @throws IOException if an error occurs
*/
public static byte[] serialize(T obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
serialize(out, obj, false);
return out.toByteArray();
}
/**
* Deserializes an object from a stream.
*
* @param in the stream to deserialize from
* @param close specifies whether the stream should be closed after reading
* @param the object type
* @return the deserialized object
* @throws IOException if an error occurs
*/
@SuppressWarnings("unchecked")
public static T deserialize(InputStream in, boolean close) throws IOException {
BufferedInputStream bis = null;
ObjectInputStream ois = null;
try {
bis = new BufferedInputStream(in);
ois = new ObjectInputStream(bis);
return (T)ois.readObject();
} catch (ClassNotFoundException cnfe) {
throw new IOException("can't read object from stream", cnfe);
} finally {
Streams.closeSilently(ois, bis, close ? in : null);
}
}
/**
* De-serializes an object from its byte representation.
*
* The serialized bytes should have been obtained using
* the {@link #serialize} method.
*
* @param bytes the serialized bytes
* @param the object type
* @return the de-serialized object
* @throws IOException if an error occurs
*/
public static T deserialize(byte[] bytes) throws IOException {
return deserialize(new ByteArrayInputStream(bytes), false);
}
/**
* Returns an InputStream whose content is the concatenation
* of all the given streams' content, in order.
*
* @param in the input streams from which the data is read (in order)
* @return an InputStream whose content is read from the given streams
*/
public static InputStream concat(final InputStream... in) {
return new InputStream() {
int i;
@Override
public int read() throws IOException {
for ( ; i < in.length; i++) {
int b = in[i].read();
if (b > -1)
return b;
}
return -1;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
for ( ; i < in.length; i++) {
int count = in[i].read(b, off, len);
if (count > -1)
return count;
}
return -1;
}
@Override
public int available() throws IOException {
return i < in.length ? in[i].available() : 0;
}
@Override
public void close() throws IOException {
closeSilently(in);
}
};
}
}