org.kiwiproject.io.KiwiIO Maven / Gradle / Ivy
Show all versions of kiwi Show documentation
package org.kiwiproject.io;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* Static I/O utilities.
*
* The {@code closeQuietly} methods that accept {@link Closeable} were copied directly from Apache Commons I/O and
* the deprecation warnings and annotations removed. While they should not be used often, sometimes they might come in
* handy so we want to keep them around for posterity. Slight style modifications were made (e.g. replace {@code obj != null}
* checks with {@code nonNull(obj}, etc. as well as adding logging. Did not bother copying all the {@code closeQuietly}
* methods that took a specific class such as {@link java.io.Reader}, {@link java.io.Writer}, {@link java.net.Socket}, etc.
* They all implement {@link Closeable} and were probably only there because those specific classes pre-dated Java 5 when
* {@link Closeable} was added to the JDK, and we assume early (pre-Java 5) versions of {@code IOUtils} provided them.
*/
@UtilityClass
@Slf4j
public class KiwiIO {
/**
* Closes a Closeable
unconditionally.
*
* Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is typically used in
* finally blocks.
*
* Example code:
*
*
* Closeable closeable = null;
* try {
* closeable = new FileReader("foo.txt");
* // process closeable
* closeable.close();
* } catch (Exception e) {
* // error handling
* } finally {
* IOUtils.closeQuietly(closeable);
* }
*
*
* Closing all streams:
*
*
* try {
* return IOUtils.copy(inputStream, outputStream);
* } finally {
* IOUtils.closeQuietly(inputStream);
* IOUtils.closeQuietly(outputStream);
* }
*
*
* @param closeable the objects to close, may be null or already closed
* @implNote Copied from Apache Commons I/O's IOUtils once it became deprecated with the message "Please use
* the try-with-resources statement or handle suppressed exceptions manually."
* @see Throwable#addSuppressed(java.lang.Throwable)
*/
public static void closeQuietly(final Closeable closeable) {
try {
if (nonNull(closeable)) {
closeable.close();
}
} catch (final IOException ioe) {
logCloseException(closeable.getClass(), ioe);
}
}
/**
* Closes a Closeable
unconditionally.
*
* Equivalent to {@link Closeable#close()}, except any exceptions will be ignored.
*
* This is typically used in finally blocks to ensure that the closeable is closed
* even if an Exception was thrown before the normal close statement was reached.
*
* It should not be used to replace the close statement(s)
* which should be present for the non-exceptional case.
*
* It is only intended to simplify tidying up where normal processing has already failed
* and reporting close failure as well is not necessary or useful.
*
* Example code:
*
*
* Closeable closeable = null;
* try {
* closeable = new FileReader("foo.txt");
* // processing using the closeable; may throw an Exception
* closeable.close(); // Normal close - exceptions not ignored
* } catch (Exception e) {
* // error handling
* } finally {
* IOUtils.closeQuietly(closeable); // In case normal close was skipped due to Exception
* }
*
*
* Closing all streams:
*
*
* try {
* return IOUtils.copy(inputStream, outputStream);
* } finally {
* IOUtils.closeQuietly(inputStream, outputStream);
* }
*
*
* @param closeables the objects to close, may be null or already closed
* @implNote Copied from Apache Commons I/O's IOUtils once it became deprecated with the message "Please use
* the try-with-resources statement or handle suppressed exceptions manually."
* @see #closeQuietly(Closeable)
* @see Throwable#addSuppressed(java.lang.Throwable)
*/
public static void closeQuietly(final Closeable... closeables) {
if (isNull(closeables)) {
return;
}
for (final Closeable closeable : closeables) {
closeQuietly(closeable);
}
}
/**
* Closes an {@link XMLStreamReader} unconditionally.
*
* Since {@link XMLStreamReader} does not implement {@link Closeable}, you cannot use a try-with-resources.
*
* @param xmlStreamReader the {@link XMLStreamReader} to close
* @see Throwable#addSuppressed(java.lang.Throwable)
*/
public static void closeQuietly(XMLStreamReader xmlStreamReader) {
if (nonNull(xmlStreamReader)) {
try {
xmlStreamReader.close();
} catch (Exception e) {
logCloseException(XMLStreamReader.class, e);
}
}
}
/**
* Closes an {@link XMLStreamWriter} unconditionally.
*
* Since {@link XMLStreamWriter} does not implement {@link Closeable}, you cannot use a try-with-resources.
*
* @param xmlStreamWriter the {@link XMLStreamWriter} to close
* @see Throwable#addSuppressed(java.lang.Throwable)
*/
public static void closeQuietly(XMLStreamWriter xmlStreamWriter) {
if (nonNull(xmlStreamWriter)) {
try {
xmlStreamWriter.close();
} catch (Exception e) {
logCloseException(XMLStreamWriter.class, e);
}
}
}
private static void logCloseException(Class> typeOfObject, Exception ex) {
String typeSimpleName = typeOfObject.getSimpleName();
LOG.warn("Unexpected error while attempting to close {} quietly (use DEBUG-level for stack trace): {}",
typeSimpleName, ex.getMessage());
LOG.debug("Error closing {} instance", typeSimpleName, ex);
}
/**
* Return a newly constructed {@link ByteArrayInputStream} containing the given {@code lines} separated by
* the {@link System#lineSeparator()}.
*
* @param lines the lines to convert
* @return a ByteArrayInputStream containing the given lines
*/
public static ByteArrayInputStream newByteArrayInputStreamOfLines(String... lines) {
if (lines.length == 0) {
return emptyByteArrayInputStream();
}
String joined = Arrays.stream(lines).collect(joining(System.lineSeparator()));
byte[] buffer = joined.getBytes(StandardCharsets.UTF_8);
return new ByteArrayInputStream(buffer);
}
/**
* Return a newly constructed, empty {@link ByteArrayInputStream}.
*
* @return new ByteArrayInputStream
*/
public static ByteArrayInputStream emptyByteArrayInputStream() {
return new ByteArrayInputStream(new byte[0]);
}
/**
* Return a {@link List} of {@link String}s from the error stream of the given {@link Process} using {@code UTF-8}
* for the String encoding.
*
* @param process the process
* @return the list of UTF-8 encoded strings from the process' error stream
*/
public static List readLinesFromErrorStreamOf(Process process) {
return readLinesFrom(process.getErrorStream(), StandardCharsets.UTF_8);
}
/**
* Return a {@link List} of {@link String}s from the error stream of the given {@link Process} using the specified
* {@link Charset} for the String encoding.
*
* @param process the process
* @param charset the charset
* @return the list of UTF-8 encoded strings from the process' error stream
*/
public static List readLinesFromErrorStreamOf(Process process, Charset charset) {
return readLinesFrom(process.getErrorStream(), charset);
}
/**
* Return a {@link List} of {@link String}s from the input stream of the given {@link Process} using {@code UTF-8}
* for the String encoding.
*
* @param process the process
* @return the list of UTF-8 encoded strings from the process' input stream
*/
public static List readLinesFromInputStreamOf(Process process) {
return readLinesFrom(process.getInputStream(), StandardCharsets.UTF_8);
}
/**
* Return a {@link List} of {@link String}s from the input stream of the given {@link Process} using the specified
* {@link Charset} for the String encoding.
*
* @param process the process
* @param charset the charset
* @return the list of UTF-8 encoded strings from the process' input stream
*/
public static List readLinesFromInputStreamOf(Process process, Charset charset) {
return readLinesFrom(process.getInputStream(), charset);
}
/**
* Return a {@link List} of {@link String}s from the input stream using the specified {@link Charset} for the
* String encoding.
*
* @param stream the stream
* @param charset the charset
* @return a list of strings from the input stream, encoded using the specified charset
*/
public static List readLinesFrom(InputStream stream, Charset charset) {
return streamLinesFrom(stream, charset).collect(toList());
}
/**
* Return a {@link Stream} of {@link String}s from the error stream of the given {@link Process} using {@code UTF-8}
* for the String encoding.
*
* @param process the process
* @return the stream of UTF-8 encoded strings from the process' error stream
*/
public static Stream streamLinesFromErrorStreamOf(Process process) {
return streamLinesFrom(process.getErrorStream(), StandardCharsets.UTF_8);
}
/**
* Return a {@link Stream} of {@link String}s from the error stream of the given {@link Process} using the specified
* {@link Charset} for the String encoding.
*
* @param process the process
* @param charset the charset
* @return the stream of strings from the process' error stream, encoded using the specified charset
*/
public static Stream streamLinesFromErrorStreamOf(Process process, Charset charset) {
return streamLinesFrom(process.getErrorStream(), charset);
}
/**
* Return a {@link Stream} of {@link String}s from the input stream of the given {@link Process} using {@code UTF-8}
* for the String encoding.
*
* @param process the process
* @return the stream of UTF-8 encoded strings from the process' input stream
*/
public static Stream streamLinesFromInputStreamOf(Process process) {
return streamLinesFrom(process.getInputStream(), StandardCharsets.UTF_8);
}
/**
* Return a {@link Stream} of {@link String}s from the input stream of the given {@link Process} using the specified
* {@link Charset} for the String encoding.
*
* @param process the process
* @param charset the charset
* @return the stream of strings from the process' input stream, encoded using the specified charset
*/
public static Stream streamLinesFromInputStreamOf(Process process, Charset charset) {
return streamLinesFrom(process.getInputStream(), charset);
}
/**
* Return a {@link Stream} of {@link String}s from the given {@link InputStream} using the specified {@link Charset}
* for the String encoding.
*
* @param stream the stream
* @param charset the charset
* @return the stream of strings from the input stream, encoded using the specified charset
*/
public static Stream streamLinesFrom(InputStream stream, Charset charset) {
return new BufferedReader(new InputStreamReader(stream, charset)).lines();
}
/**
* Read the input stream of the give {@link Process} as a String using {@code UTF-8} as the String encoding.
*
* Note that process output may contain one or more lines, which will therefore include line termination
* characters within or at the end of the returned string.
*
* @param process the process
* @return the process' input stream as a UTF-8 encoded string
* @see Process#getInputStream()
*/
public static String readInputStreamOf(Process process) {
return readInputStreamOf(process, StandardCharsets.UTF_8);
}
/**
* Read the input stream of the give {@link Process} as a String using the the specified {@link Charset} for the
* string encoding.
*
* Note that process output may contain one or more lines, which will therefore include line termination
* characters within or at the end of the returned string.
*
* @param process the process
* @param charset the charset
* @return the process' input stream as a string, encoded using the specified charset
* @see Process#getInputStream()
*/
public static String readInputStreamOf(Process process, Charset charset) {
return readInputStreamAsString(process.getInputStream(), charset);
}
/**
* Read the error stream of the give {@link Process} as a String using {@code UTF-8} as the string encoding.
*
* Note that process output may contain one or more lines, which will therefore include line termination
* characters within or at the end of the returned string.
*
* @param process the process
* @return the process' error stream as a UTF-8 encoded string
* @see Process#getErrorStream()
*/
public static String readErrorStreamOf(Process process) {
return readErrorStreamOf(process, StandardCharsets.UTF_8);
}
/**
* Read the error stream of the give {@link Process} as a String using the the specified {@link Charset} for the
* string encoding.
*
* Note that process output may contain one or more lines, which will therefore include line termination
* characters within or at the end of the returned string.
*
* @param process the process
* @param charset the charset
* @return the process' error stream as a string, encoded using the specified charset
* @see Process#getErrorStream()
*/
public static String readErrorStreamOf(Process process, Charset charset) {
return readInputStreamAsString(process.getErrorStream(), charset);
}
/**
* Convert the given {@link InputStream} to a {@code UTF-8} encoded String.
*
* @param inputStream the input stream
* @return the input stream as a UTF-8 encoded string
*/
public static String readInputStreamAsString(InputStream inputStream) {
return readInputStreamAsString(inputStream, StandardCharsets.UTF_8);
}
/**
* Convert the given {@link InputStream} to a String using the given {@link Charset} for the string encoding.
*
* @param inputStream the input stream
* @param charset the charset
* @return the input stream as a string, encoded using the specified charset
*/
public static String readInputStreamAsString(InputStream inputStream, Charset charset) {
try {
var outputStream = new ByteArrayOutputStream();
inputStream.transferTo(outputStream);
return outputStream.toString(charset);
} catch (IOException e) {
throw new UncheckedIOException("Error converting InputStream to String using Charset " + charset, e);
}
}
}