Streams.java
package xyz.apollosoftware.bibliothiki.compression.utils;
import org.jspecify.annotations.NonNull;
import xyz.apollosoftware.bibliothiki.compression.CompressionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;
/**
* Utility methods for working with streams.
*/
public final class Streams {
/**
* The size of the buffers to transfer.
*/
private static final int BUFFER_SIZE = 4096;
private Streams() {}
/**
* Pipe n bytes from the given {@link InputStream} to the given
* {@link OutputStream}.
*
* <p>
* If n is less than or equal to zero, this method immediately returns zero.
*
* @param input The input stream to read bytes from.
* @param output The output stream to write bytes to.
* @param n The number of bytes to pipe.
* @return The number of bytes that were piped.
*/
public static long pipeNBytes(@NonNull InputStream input, @NonNull OutputStream output, final long n) {
Objects.requireNonNull(input, "The input stream must not be null.");
Objects.requireNonNull(output, "The output stream must not be null.");
// If there are no bytes to copy, do nothing.
if (n <= 0) return 0;
byte[] buffer = new byte[BUFFER_SIZE];
long bytesRead = 0;
while (bytesRead < n) {
// Safely compute how much to transfer (it should be any number of
// bytes up to BUFFER_SIZE, ensuring overflows do not cause the
// wrong amount).
final long remaining = n - bytesRead;
// Read the requested bytes from the buffer.
int result;
try {
result = input.read(buffer, 0, remaining >= BUFFER_SIZE ? BUFFER_SIZE : (int) remaining);
} catch (IOException ex) {
ifPossible(input::close);
ifPossible(output::close);
throw new CompressionException("Failed to read entry from archive", ex);
}
// Ensure we do not receive a premature EOF.
if (result == -1) {
ifPossible(input::close);
ifPossible(output::close);
throw new CompressionException("Unexpected EOF");
}
// Write the buffer to the output stream.
try {
output.write(buffer, 0, result);
bytesRead += result;
} catch (IOException ex) {
ifPossible(input::close);
ifPossible(output::close);
throw new CompressionException("Failed to write bytes to output stream", ex);
}
}
return bytesRead;
}
@FunctionalInterface
interface IfPossible {
void execute() throws IOException;
}
private static void ifPossible(@NonNull IfPossible ifPossible) {
try {
ifPossible.execute();
} catch (IOException ignored) {
}
}
}