|
|
@@ -17,6 +17,8 @@ import java.nio.charset.CharsetEncoder;
|
|
|
import java.nio.charset.CoderResult;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.function.Consumer;
|
|
|
+
|
|
|
+import lombok.experimental.UtilityClass;
|
|
|
import net.ranides.assira.events.EventListener;
|
|
|
import net.ranides.assira.functional.checked.CheckedRunnable;
|
|
|
import net.ranides.assira.generic.CompareUtils;
|
|
|
@@ -24,13 +26,21 @@ import net.ranides.assira.trace.ExceptionUtils;
|
|
|
import net.ranides.assira.trace.LoggerUtils;
|
|
|
|
|
|
/**
|
|
|
+ * Utility methods for I/O streams
|
|
|
*
|
|
|
* @author Ranides Atterwim {@literal <ranides@gmail.com>}
|
|
|
*/
|
|
|
-public final class IOStreams {
|
|
|
-
|
|
|
+@UtilityClass
|
|
|
+public class IOStreams {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This output stream will silently discard every written data.
|
|
|
+ */
|
|
|
public static final OutputStream NULL = new NULLStream();
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This input stream does not contains any data and signals EOF.
|
|
|
+ */
|
|
|
public static final InputStream EMPTY = new EmptyStream();
|
|
|
|
|
|
private static final int COPY_BUFFER = 4096;
|
|
|
@@ -40,25 +50,62 @@ public final class IOStreams {
|
|
|
private static final int WRITER_BUFFER = 512;
|
|
|
|
|
|
private static final org.slf4j.Logger LOGGER = LoggerUtils.getLogger();
|
|
|
-
|
|
|
- private IOStreams() {
|
|
|
- /* utility class */
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
+ * Reads all bytes from stream and returns them in array.
|
|
|
+ * Blocks until EOF is reached.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @return data
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
public static byte[] read(InputStream input) throws IOException {
|
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(input.available());
|
|
|
copy(input, bos, null);
|
|
|
return bos.toByteArray();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copies all bytes from input into output stream.
|
|
|
+ * Blocks until EOF is reached.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @return number of copied bytes
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
public static long copy(InputStream input, OutputStream output) throws IOException {
|
|
|
return copy(input, output, null);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Copies at most "size" bytes form input to output stream.
|
|
|
+ * It could copy less than "size" if EOF is reached.
|
|
|
+ * Blocks until "size" bytes is copied or EOF is reached.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @param size maximum number of bytes to copy
|
|
|
+ * @return number of copied bytes
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
public static long copy(InputStream input, OutputStream output, long size) throws IOException {
|
|
|
return copy(input, output, size, null);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Copies all bytes from input into output stream.
|
|
|
+ * Blocks until EOF is reached.
|
|
|
+ *
|
|
|
+ * Calls "callback" about every successfully copied chunk of data.
|
|
|
+ * Usefull if we want to be informed about progress.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @param callback callback
|
|
|
+ * @return number of copied bytes
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
public static long copy(InputStream input, OutputStream output, Consumer<Long> callback) throws IOException {
|
|
|
byte[] buffer = new byte[COPY_BUFFER];
|
|
|
int n;
|
|
|
@@ -73,6 +120,21 @@ public final class IOStreams {
|
|
|
return s;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Copies at most "size" bytes form input to output stream.
|
|
|
+ * It could copy less than "size" if EOF is reached.
|
|
|
+ * Blocks until "size" bytes is copied or EOF is reached.
|
|
|
+ *
|
|
|
+ * Calls "callback" about every successfully copied chunk of data.
|
|
|
+ * Usefull if we want to be informed about progress.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @param size size
|
|
|
+ * @param callback callback
|
|
|
+ * @return number of copied bytes
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
public static long copy(InputStream input, OutputStream output, final long size, Consumer<Long> callback) throws IOException {
|
|
|
byte[] buffer = new byte[COPY_BUFFER];
|
|
|
|
|
|
@@ -97,12 +159,23 @@ public final class IOStreams {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- public static boolean equivalent(InputStream stream1, InputStream stream2) throws IOException {
|
|
|
- return equivalent(stream1, stream2, COPY_BUFFER);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Compares content of two streams and returns "tue" if they are equal.
|
|
|
+ *
|
|
|
+ * Please note, that content of both streams will be consumed.
|
|
|
+ * It stops reading before EOF if difference is found.
|
|
|
+ *
|
|
|
+ * @param stream1 stream1
|
|
|
+ * @param stream2 stream2
|
|
|
+ * @return boolean
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
+ public static boolean equals(InputStream stream1, InputStream stream2) throws IOException {
|
|
|
+ return equals(stream1, stream2, COPY_BUFFER);
|
|
|
}
|
|
|
|
|
|
- static boolean equivalent(InputStream stream1, InputStream stream2, int bufsize) throws IOException {
|
|
|
+ private static boolean equals(InputStream stream1, InputStream stream2, int bufsize) throws IOException {
|
|
|
byte[] bleft = new byte[bufsize];
|
|
|
byte[] bright = new byte[bufsize];
|
|
|
while (true) {
|
|
|
@@ -120,18 +193,48 @@ public final class IOStreams {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates new stream which delegates all I/O operations to "is" and
|
|
|
+ * sends event to listener after every I/O call.
|
|
|
+ *
|
|
|
+ * @param is is
|
|
|
+ * @param listener listener
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static InputStream observe(InputStream is, EventListener<? super IOEvent> listener) {
|
|
|
return new InputObserver(is, listener);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new stream which delegates all I/O operations to "os" and
|
|
|
+ * sends event to listener after every I/O call.
|
|
|
+ *
|
|
|
+ * @param os os
|
|
|
+ * @param listener listener
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static OutputStream observe(OutputStream os, EventListener<? super IOEvent> listener) {
|
|
|
return new OutputObserver(os, listener);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates multiplexed stream which writes data to all provided streams.
|
|
|
+ *
|
|
|
+ * @param streams streams
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static OutputStream join(OutputStream... streams) {
|
|
|
return new OutputJoiner(streams);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Binds provided stream to stdout.
|
|
|
+ * Every call to "System.out" will send data to stdout and provided stream.
|
|
|
+ *
|
|
|
+ * @param ostream ostream
|
|
|
+ * @param <S> S
|
|
|
+ * @return binded stream
|
|
|
+ */
|
|
|
public static <S extends OutputStream> S tee(S ostream) {
|
|
|
try {
|
|
|
System.setOut(new PrintStream(join(System.out, ostream), false, Charset.defaultCharset().name()));
|
|
|
@@ -140,15 +243,37 @@ public final class IOStreams {
|
|
|
throw ExceptionUtils.rethrow(cause);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new stream binded to stdout.
|
|
|
+ * Every call to "System.out" will send data to stdout and created stream.
|
|
|
+ *
|
|
|
+ * @return binded stream
|
|
|
+ */
|
|
|
public static StringOutput tee() {
|
|
|
return tee(new StringOutput());
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new stream which will write all binary data into provided writer,
|
|
|
+ * using provided charset to encode characters on the fly.
|
|
|
+ *
|
|
|
+ * @param writer writer
|
|
|
+ * @param cs cs
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static OutputStream stream(Writer writer, Charset cs) {
|
|
|
return new WriterStream(writer, cs);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new stream which will read all characters from provided reader,
|
|
|
+ * and convert them on the fly into bytes using provided charset.
|
|
|
+ *
|
|
|
+ * @param reader reader
|
|
|
+ * @param cs cs
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static InputStream stream(Reader reader, Charset cs) {
|
|
|
CharsetEncoder ce = cs.newEncoder();
|
|
|
float bpc = ce.maxBytesPerChar();
|
|
|
@@ -167,11 +292,36 @@ public final class IOStreams {
|
|
|
return new ReaderStream(reader, ce);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new background thread which will copy all data from "input" to "output".
|
|
|
+ * You should always terminate created "pipe" threads.
|
|
|
+ * Created thread closes correctly both streams when terminated.
|
|
|
+ *
|
|
|
+ * Please note that returned thread is already started.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @return thread
|
|
|
+ */
|
|
|
public static Thread pipe(InputStream input, OutputStream output) {
|
|
|
return pipe(input, output, (Exception e) -> LOGGER.error("Exception ignored in #pipe", e));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new background thread which will copy all data from "input" to "output".
|
|
|
+ * You should always terminate created "pipe" threads.
|
|
|
+ * Created thread closes correctly both streams when terminated.
|
|
|
+ *
|
|
|
+ * It sends information about exceptions to "handler".
|
|
|
+ *
|
|
|
+ * Please note that returned thread is already started.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @param handler handler
|
|
|
+ * @return thread
|
|
|
+ */
|
|
|
public static Thread pipe(InputStream input, OutputStream output, Consumer<Exception> handler) {
|
|
|
Thread thread = new Thread(() -> {
|
|
|
try {
|
|
|
@@ -186,14 +336,32 @@ public final class IOStreams {
|
|
|
thread.start();
|
|
|
return thread;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new background thread which will copy all data from "input" to "output".
|
|
|
+ * You should always terminate created "pipe" threads.
|
|
|
+ * Created thread closes correctly both streams when terminated.
|
|
|
+ *
|
|
|
+ * Please note that returned thread is already started.
|
|
|
+ *
|
|
|
+ * It sends information about operations to "listener".
|
|
|
+ * It sends "seek" events when copy.
|
|
|
+ * It sends "flush" event after copy.
|
|
|
+ * It sends "failure" event on error.
|
|
|
+ * It sends "close" event before termination.
|
|
|
+ *
|
|
|
+ * @param input input
|
|
|
+ * @param output output
|
|
|
+ * @param listener listener
|
|
|
+ * @return thread
|
|
|
+ */
|
|
|
public static Thread pipe(InputStream input, OutputStream output, EventListener<? super IOEvent> listener) {
|
|
|
if(null == listener) {
|
|
|
return pipe(input, output);
|
|
|
}
|
|
|
Thread thread = new Thread(() -> {
|
|
|
try {
|
|
|
- copy(input, output, p -> listener.handleEvent(IOEvent.move(input, p)));
|
|
|
+ copy(input, output, p -> listener.handleEvent(IOEvent.seek(input, p)));
|
|
|
listener.handleEvent(IOEvent.flush(output));
|
|
|
} catch(IOException cause) {
|
|
|
listener.handleEvent(IOEvent.failure(cause));
|
|
|
@@ -209,7 +377,11 @@ public final class IOStreams {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Wraps stream (wrapper does not close stream after use).
|
|
|
+ * Creates wrapper stream with limited content to specified range.
|
|
|
+ * Skips first "begin" bytes and signals EOF after reaching "end".
|
|
|
+ *
|
|
|
+ * Please note that it does not close "istream"!
|
|
|
+ *
|
|
|
* @param istream istream
|
|
|
* @param begin begin
|
|
|
* @param end end
|
|
|
@@ -222,9 +394,13 @@ public final class IOStreams {
|
|
|
}
|
|
|
return new SubInput(istream, begin, end, false);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * Wraps stream (wrapper closes stream after use).
|
|
|
+ * Creates wrapper stream with limited content to specified range.
|
|
|
+ * Skips first "begin" bytes and signals EOF after reaching "end".
|
|
|
+ *
|
|
|
+ * Please note that it closes "istream"!
|
|
|
+ *
|
|
|
* @param istream istream
|
|
|
* @param begin begin
|
|
|
* @param end end
|
|
|
@@ -238,6 +414,13 @@ public final class IOStreams {
|
|
|
return new SubInput(istream, begin, end, true);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Skips "n" bytes in stream.
|
|
|
+ *
|
|
|
+ * @param istream istream
|
|
|
+ * @param n n
|
|
|
+ * @throws IOException on error
|
|
|
+ */
|
|
|
public static void skip(InputStream istream, long n) throws IOException {
|
|
|
long skipped = istream.skip(n);
|
|
|
if(skipped != n) {
|
|
|
@@ -245,6 +428,14 @@ public final class IOStreams {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates new stream wrapper which delegates all operations to "ostream" and
|
|
|
+ * additionally calls provided callback when "close" is called.
|
|
|
+ *
|
|
|
+ * @param ostream ostream
|
|
|
+ * @param onClose onClose
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static OutputStream onClose(OutputStream ostream, CheckedRunnable<IOException> onClose) {
|
|
|
return new OutputWrapper(ostream){
|
|
|
@Override
|
|
|
@@ -255,6 +446,14 @@ public final class IOStreams {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates new stream wrapper which delegates all operations to "ostream" and
|
|
|
+ * additionally calls provided callback when "close" is called.
|
|
|
+ *
|
|
|
+ * @param istream istream
|
|
|
+ * @param onClose onClose
|
|
|
+ * @return stream
|
|
|
+ */
|
|
|
public static InputStream onClose(InputStream istream, CheckedRunnable<IOException> onClose) {
|
|
|
return new InputWrapper(istream){
|
|
|
@Override
|