org.apache.juneau.common.internal.IOUtils Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you 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. *
// ***************************************************************************************************************************
package org.apache.juneau.common.internal;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
/**
* Various I/O related utility methods.
*/
public final class IOUtils {
/** UTF-8 charset */
public static final Charset UTF8 = Charset.forName("UTF-8");
/** Reusable empty input stream. */
public static final InputStream EMPTY_INPUT_STREAM = new InputStream() {
@Override
public int read() {
return -1; // end of stream
}
};
private static final int BUFF_SIZE = 1024;
private static final ThreadLocal BYTE_BUFFER_CACHE = (Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new ThreadLocal<>());
private static final ThreadLocal CHAR_BUFFER_CACHE = (Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new ThreadLocal<>());
static final AtomicInteger BYTE_BUFFER_CACHE_HITS = new AtomicInteger();
static final AtomicInteger BYTE_BUFFER_CACHE_MISSES = new AtomicInteger();
static final AtomicInteger CHAR_BUFFER_CACHE_HITS = new AtomicInteger();
static final AtomicInteger CHAR_BUFFER_CACHE_MISSES = new AtomicInteger();
static {
SystemUtils.shutdownMessage(()->"Byte buffer cache: hits="+BYTE_BUFFER_CACHE_HITS.get()+", misses=" + BYTE_BUFFER_CACHE_MISSES);
SystemUtils.shutdownMessage(()->"Char buffer cache: hits="+CHAR_BUFFER_CACHE_HITS.get()+", misses=" + CHAR_BUFFER_CACHE_MISSES);
}
/** Reusable empty reader. */
public static final Reader EMPTY_READER = new Reader() {
@Override
public int read() {
return -1; // end of stream
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return -1; // end of stream
}
@Override
public void close() throws IOException {}
};
//-----------------------------------------------------------------------------------------------------------------
// Piping utilities.
//-----------------------------------------------------------------------------------------------------------------
/**
* Pipes the contents of the specified Reader to the specified file.
*
* @param in
* The reader to pipe from.
*
Can be null .
*
Reader is automatically closed.
* @param out
* The file to write the output to.
*
Can be null .
* @return
* The number of characters piped.
* @throws IOException Thrown by underlying stream.
*/
public static long pipe(Reader in, File out) throws IOException {
if (out == null || in == null)
return 0;
try (Writer w = FileWriterBuilder.create(out).buffered().build()) {
return pipe(in, w);
}
}
/**
* Pipes the contents of the specified Reader to the specified Writer .
*
* @param in
* The reader to pipe from.
*
Can be null .
*
Reader is automatically closed.
* @param out
* The file to write the output to.
*
Can be null .
*
Writer is flushed but not automatically closed.
* @return
* The number of characters piped.
* @throws IOException Thrown by underlying stream.
*/
public static long pipe(Reader in, Writer out) throws IOException {
if (out == null || in == null)
return 0;
long total = 0;
try (Reader in2 = in) {
char[] buffer = charBuffer(-1);
int readLen;
while ((readLen = in.read(buffer)) != -1) {
out.write(buffer, 0, readLen);
total += readLen;
}
}
out.flush();
return total;
}
/**
* Pipes the contents of the specified Reader to the specified Writer .
*
* @param in
* The reader to pipe from.
*
Can be null .
*
Reader is automatically closed.
* @param out
* The file to write the output to.
*
Can be null .
*
Writer is flushed but not automatically closed.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return
* The number of characters piped.
*/
public static long pipe(Reader in, Writer out, Consumer onException) {
try {
return pipe(in, out);
} catch (IOException e) {
onException.accept(e);
return -1;
}
}
/**
* Pipes the contents of the specified Reader to the specified Writer a line at a time.
*
*
* Writer is flushed after every line. Typically useful when writing to consoles.
*
* @param in
* The reader to pipe from.
*
Can be null .
*
Reader is automatically closed.
* @param out
* The file to write the output to.
*
Can be null .
*
Writer is flushed but not automatically closed.
* @return
* The number of characters piped.
* @throws IOException Thrown by underlying stream.
*/
public static long pipeLines(Reader in, Writer out) throws IOException {
if (in == null || out == null)
return 0;
long total = 0;
try (Reader in2 = in) {
try (Scanner s = new Scanner(in2)) {
while (s.hasNextLine()) {
String l = s.nextLine();
if (l != null) {
out.write(l);
out.write("\n");
out.flush();
total += l.length() + 1;
}
}
}
}
return total;
}
/**
* Pipes the contents of the specified input stream to the writer.
*
* @param in
* The stream to pipe from.
*
Can be null .
*
Streams is automatically closed.
* @param out
* The writer to pipe to.
*
Can be null .
*
Stream is not automatically closed.
* @return
* The number of bytes written.
* @throws IOException Thrown by underlying stream.
*/
public static long pipe(InputStream in, Writer out) throws IOException {
if (in == null || out == null)
return 0;
return pipe(new InputStreamReader(in, UTF8), out);
}
/**
* Pipes the contents of the specified input stream to the writer.
*
* @param in
* The stream to pipe from.
*
Can be null .
*
Streams is automatically closed.
* @param out
* The writer to pipe to.
*
Can be null .
*
Stream is not automatically closed.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return
* The number of bytes written.
*/
public static long pipe(InputStream in, Writer out, Consumer onException) {
try {
if (in == null || out == null)
return 0;
return pipe(new InputStreamReader(in, UTF8), out);
} catch (IOException e) {
onException.accept(e);
return -2;
}
}
/**
* Pipes the specified input stream to the specified output stream.
*
*
* Either stream is not automatically closed.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is automatically closed.
* @param out
* The output stream.
*
Can be null .
*
Stream is not automatically closed.
* @return The number of bytes written.
* @throws IOException If thrown from either stream.
*/
public static long pipe(InputStream in, OutputStream out) throws IOException {
try (InputStream in2 = in) {
return pipe(in, out, -1);
}
}
/**
* Pipes the specified input stream to the specified output stream.
*
*
* Either stream is not automatically closed.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is automatically closed.
* @param out
* The output stream.
*
Can be null .
*
Stream is not automatically closed.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return The number of bytes written.
*/
public static long pipe(InputStream in, OutputStream out, Consumer onException) {
try {
try (InputStream in2 = in) {
return pipe(in, out, -1);
}
} catch (IOException e) {
onException.accept(e);
return -1;
}
}
/**
* Pipes the specified input stream to the specified output stream.
*
*
* Either stream is not automatically closed.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is not automatically closed.
* @param out
* The output stream.
*
Can be null .
*
Stream is not automatically closed.
* @param maxBytes
* The maximum number of bytes or -1 to read the entire input stream.
* @return The number of bytes written.
* @throws IOException If thrown from either stream.
*/
public static long pipe(InputStream in, OutputStream out, long maxBytes) throws IOException {
if (in == null || out == null)
return 0;
byte[] buffer = byteBuffer((int)maxBytes);
int readLen;
long total = 0;
if (maxBytes < 0) {
while ((readLen = in.read(buffer)) != -1) {
out.write(buffer, 0, readLen);
total += readLen;
}
} else {
long remaining = maxBytes;
while (remaining > 0) {
readLen = in.read(buffer, 0, buffSize(remaining));
if (readLen == -1)
break;
out.write(buffer, 0, readLen);
total += readLen;
remaining -= readLen;
}
}
out.flush();
return total;
}
/**
* Pipes the specified reader to the specified output stream.
*
* @param in
* The input reader.
*
Can be null .
*
Stream is automatically closed.
* @param out
* The output stream.
*
Can be null .
*
Stream is not automatically closed.
* @return The number of bytes written.
* @throws IOException If thrown from output stream.
*/
public static long pipe(Reader in, OutputStream out) throws IOException {
if (in == null || out == null)
return 0;
long total = 0;
try (Reader in2 = in) {
OutputStreamWriter osw = new OutputStreamWriter(out, UTF8);
int i;
char[] b = charBuffer(-1);
while ((i = in.read(b)) > 0) {
total += i;
osw.write(b, 0, i);
}
osw.flush();
}
return total;
}
/**
* Pipes the specified reader to the specified output stream.
*
* @param in
* The input reader.
*
Can be null .
*
Stream is automatically closed.
* @param out
* The output stream.
*
Can be null .
*
Stream is not automatically closed.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return The number of bytes written.
*/
public static long pipe(Reader in, OutputStream out, Consumer onException) {
try {
return pipe(in, out);
} catch (IOException e) {
onException.accept(e);
return -1;
}
}
/**
* Pipes the specified byte array to the specified output stream.
*
* @param in
* The input byte array.
*
Can be null .
* @param out
* The output stream.
*
Can be null .
*
Stream is not automatically closed.
* @param maxBytes
* The maximum number of bytes or -1 to read the entire byte array.
* @return The number of bytes written.
* @throws IOException If thrown from output stream.
*/
public static long pipe(byte[] in, OutputStream out, int maxBytes) throws IOException {
if (in == null || out == null)
return 0;
int length = (maxBytes < 0 || maxBytes > in.length ) ? in.length : maxBytes;
out.write(in, 0, length);
return length;
}
//-----------------------------------------------------------------------------------------------------------------
// Reading utilities.
//-----------------------------------------------------------------------------------------------------------------
/**
* Pipes the specified object to the specified output stream.
*
* @param in
* The input byte array.
*
Can be null or any of the following types:
*
* - {@link Reader}
*
- {@link InputStream}
*
- {@link File}
*
- byte array.
*
* @return The input converted to a string.
* @throws IOException If thrown from output stream.
*/
public static String read(Object in) throws IOException {
if (in == null)
return null;
if (in instanceof Reader)
return read((Reader)in);
if (in instanceof InputStream)
return read((InputStream)in);
if (in instanceof File)
return read((File)in);
if (in instanceof byte[])
return read((byte[])in);
throw new IllegalArgumentException("Invalid type passed to read: " + in.getClass().getName());
}
/**
* Reads the specified byte array containing UTF-8 into a string.
*
* @param in
* The input.
*
Can be null .
* @return The new string, or null if the input was null.
*/
public static String read(byte[] in) {
return read(in, UTF8);
}
/**
* Reads the specified byte array into a string.
*
* @param in
* The input.
*
Can be null .
* @param charset The character set to use for decoding.
* @return The new string, or null if the input was null.
*/
public static String read(byte[] in, Charset charset) {
if (in == null)
return null;
return new String(in, charset);
}
/**
* Reads the contents of a file into a string.
*
*
* Assumes default character encoding.
*
* @param in
* The file to read.
*
Can be null .
* @return
* The contents of the reader as a string, or null if file does not exist.
* @throws IOException If a problem occurred trying to read from the reader.
*/
public static String read(File in) throws IOException {
if (in == null || ! in.exists())
return null;
try (Reader r = FileReaderBuilder.create(in).build()) {
return read(r, in.length());
}
}
/**
* Reads the contents of a reader into a string.
*
* @param in
* The input reader.
*
Can be null .
*
Stream is automatically closed.
* @return
* The contents of the reader as a string, or null if the reader was null .
* @throws IOException If a problem occurred trying to read from the reader.
*/
public static String read(Reader in) throws IOException {
try (Reader in2 = in) {
return read(in, -1);
}
}
/**
* Reads the contents of a reader into a string.
*
* @param in
* The input reader.
*
Can be null .
*
Stream is automatically closed.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return
* The contents of the reader as a string, or null if the reader was null .
*/
public static String read(Reader in, Consumer onException) {
try (Reader in2 = in) {
return read(in, -1);
} catch (IOException e) {
onException.accept(e);
return null;
}
}
/**
* Reads the specified input into a {@link String} until the end of the input is reached.
*
* @param in
* The input reader.
*
Can be null .
*
String is automatically closed.
* @param expectedLength
* Specify a positive number if the length of the input is known, or -1 if unknown.
* @return
* The contents of the reader as a string, or null if the reader was null .
* @throws IOException If a problem occurred trying to read from the reader.
*/
public static String read(Reader in, long expectedLength) throws IOException {
if (in == null)
return null;
try (Reader in2 = in) {
StringBuilder sb = new StringBuilder(buffSize(expectedLength)); // Assume they're ASCII characters.
char[] buf = charBuffer((int)expectedLength);
int i = 0;
while ((i = in2.read(buf)) != -1)
sb.append(buf, 0, i);
return sb.toString();
}
}
/**
* Reads the contents of an input stream into a string.
*
*
* Assumes UTF-8 encoding.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is automatically closed.
* @return
* The contents of the reader as a string, or null if the input stream was null .
* @throws IOException If a problem occurred trying to read from the input stream.
*/
public static String read(InputStream in) throws IOException {
return read(in, UTF8);
}
/**
* Reads the contents of an input stream into a string.
*
*
* Assumes UTF-8 encoding.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is automatically closed.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return
* The contents of the reader as a string, or null if the input stream was null .
*/
public static String read(InputStream in, Consumer onException) {
return read(in, UTF8, onException);
}
/**
* Reads the contents of an input stream into a string using the specified charset.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is automatically closed.
* @param cs
* The charset of the contents of the input stream.
* @return
* The contents of the reader as a string or null if input stream was null .
* @throws IOException If a problem occurred trying to read from the input stream.
*/
public static String read(InputStream in, Charset cs) throws IOException {
if (in == null)
return null;
try (InputStreamReader isr = new InputStreamReader(in, cs)) {
return read(isr);
}
}
/**
* Reads the contents of an input stream into a string using the specified charset.
*
* @param in
* The input stream.
*
Can be null .
*
Stream is automatically closed.
* @param cs
* The charset of the contents of the input stream.
* @param onException Consumer of any {@link IOException I/O exceptions}.
* @return
* The contents of the reader as a string or null if input stream was null .
*/
public static String read(InputStream in, Charset cs, Consumer onException) {
if (in == null)
return null;
try (InputStreamReader isr = new InputStreamReader(in, cs)) {
return read(isr);
} catch (IOException e) {
onException.accept(e);
return null;
}
}
/**
* Reads the specified input stream into the specified byte array.
*
* @param in
* The input stream to read.
*
Can be null .
*
Stream is automatically closed.
* @return A byte array containing the contents. Never null .
* @throws IOException Thrown by underlying stream.
*/
public static byte[] readBytes(InputStream in) throws IOException {
try (InputStream in2 = in) {
return readBytes(in2, -1);
}
}
/**
* Reads the specified input stream into the specified byte array.
*
* @param in
* The input stream to read.
*
Can be null .
*
Stream is not automatically closed.
* @param maxBytes
* The maximum number of bytes or -1 to read the entire stream.
* @return A byte array containing the contents. Never null .
* @throws IOException Thrown by underlying stream.
*/
public static byte[] readBytes(InputStream in, int maxBytes) throws IOException {
if (in == null)
return new byte[0];
ByteArrayOutputStream buff = new ByteArrayOutputStream(buffSize(maxBytes));
int nRead;
byte[] b = byteBuffer(maxBytes);
while ((nRead = in.read(b, 0, b.length)) != -1)
buff.write(b, 0, nRead);
buff.flush();
return buff.toByteArray();
}
/**
* Read the specified file into a byte array.
*
* @param in
* The file to read into a byte array.
* @return The contents of the file as a byte array.
* @throws IOException Thrown by underlying stream.
*/
public static byte[] readBytes(File in) throws IOException {
return readBytes(in, -1);
}
/**
* Read the specified file into a byte array.
*
* @param in
* The file to read into a byte array.
* @param maxBytes
* The maximum number of bytes to read, or -1 to read all bytes.
* @return The contents of the file as a byte array.
* @throws IOException Thrown by underlying stream.
*/
public static byte[] readBytes(File in, int maxBytes) throws IOException {
if (in == null || ! (in.exists() && in.canRead()))
return new byte[0];
try (FileInputStream is = new FileInputStream(in)) {
return readBytes(is, maxBytes);
}
}
/**
* Reads the specified input stream into the specified byte array.
*
* @param in
* The input stream to read.
*
Can be null .
*
Stream is automatically closed.
* @return A byte array containing the contents. Never null .
* @throws IOException Thrown by underlying stream.
*/
public static byte[] readBytes(Reader in) throws IOException {
if (in == null)
return new byte[0];
try (Reader in2 = in) {
return read(in2, -1).getBytes();
}
}
//-----------------------------------------------------------------------------------------------------------------
// Other utilities.
//-----------------------------------------------------------------------------------------------------------------
/**
* Wraps the specified reader in a buffered reader.
*
* @param r The reader being wrapped.
* @return
* The reader wrapped in a {@link BufferedReader}, or the original {@link Reader} if it's already a buffered
* reader.
*/
public static Reader toBufferedReader(Reader r) {
if (r == null || r instanceof BufferedReader || r instanceof StringReader)
return r;
return new BufferedReader(r);
}
/**
* Counts the number of bytes in the input stream and then closes the stream.
*
* @param is The input stream to read from.
* @return The number of bytes read.
* @throws IOException Thrown by underlying stream.
*/
public static long count(InputStream is) throws IOException {
if (is == null)
return 0;
long c = 0;
long i;
try {
while ((i = is.skip(1024)) != 0)
c += i;
} finally {
is.close();
}
return c;
}
/**
* Counts the number of characters in the reader and then closes the reader.
*
* @param r The reader to read from.
* @return The number of characters read.
* @throws IOException Thrown by underlying stream.
*/
public static long count(Reader r) throws IOException {
if (r == null)
return 0;
long c = 0;
long i;
try {
while ((i = r.skip(1024)) != 0)
c += i;
} finally {
r.close();
}
return c;
}
/**
* Close input stream and ignore any exceptions.
*
*
* No-op if input stream is null .
*
* @param is The input stream to close.
*/
public static void closeQuietly(InputStream is) {
try {
if (is != null)
is.close();
} catch (IOException e) {}
}
/**
* Close output stream and ignore any exceptions.
*
*
* No-op if output stream is null .
*
* @param os The output stream to close.
*/
public static void closeQuietly(OutputStream os) {
try {
if (os != null)
os.close();
} catch (IOException e) {}
}
/**
* Close reader and ignore any exceptions.
*
*
* No-op if reader is null .
*
* @param r The reader to close.
*/
public static void closeQuietly(Reader r) {
try {
if (r != null)
r.close();
} catch (IOException e) {}
}
/**
* Close writer and ignore any exceptions.
*
*
* No-op if writer is null .
*
* @param w The writer to close.
*/
public static void closeQuietly(Writer w) {
try {
if (w != null)
w.close();
} catch (IOException e) {}
}
/**
* Quietly close all specified input streams, output streams, readers, and writers.
*
* @param o The list of all objects to quietly close.
*/
public static void closeQuietly(Object...o) {
for (Object o2 : o) {
if (o2 instanceof InputStream)
closeQuietly((InputStream)o2);
if (o2 instanceof OutputStream)
closeQuietly((OutputStream)o2);
if (o2 instanceof Reader)
closeQuietly((Reader)o2);
if (o2 instanceof Writer)
closeQuietly((Writer)o2);
}
}
/**
* Flushes multiple output streams and writers in a single call.
*
* @param o
* The objects to flush.
* null entries are ignored.
* @throws IOException Thrown by underlying stream.
*/
public static void flush(Object...o) throws IOException {
IOException ex = null;
for (Object o2 : o) {
try {
if (o2 instanceof OutputStream)
((OutputStream)o2).flush();
if (o2 instanceof Writer)
((Writer)o2).flush();
} catch (IOException e) {
ex = e;
}
}
if (ex != null)
throw ex;
}
/**
* Close all specified input streams, output streams, readers, and writers.
*
* @param o
* The list of all objects to close.
* null entries are ignored.
* @throws IOException Thrown by underlying stream.
*/
public static void close(Object...o) throws IOException {
IOException ex = null;
for (Object o2 : o) {
try {
if (o2 instanceof InputStream)
((InputStream)o2).close();
if (o2 instanceof OutputStream)
((OutputStream)o2).close();
if (o2 instanceof Reader)
((Reader)o2).close();
if (o2 instanceof Writer)
((Writer)o2).close();
} catch (IOException e) {
ex = e;
}
}
if (ex != null)
throw ex;
}
/**
* Loads a text file from either the file system or classpath.
*
* @param name The file name.
* @param paths The paths to search.
* @return The file contents, or null if not found.
* @throws IOException Thrown by underlying stream.
*/
public static String loadSystemResourceAsString(String name, String...paths) throws IOException {
for (String path : paths) {
File p = new File(path);
if (p.exists()) {
File f = new File(p, name);
if (f.exists() && f.canRead())
return read(f);
}
}
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null)
cl = ClassLoader.getSystemClassLoader();
for (String path : paths) {
String n = ".".equals(path) ? name : path + '/' + name;
try (InputStream is = cl.getResourceAsStream(n)) {
if (is != null)
return read(is);
}
try (InputStream is = ClassLoader.getSystemResourceAsStream(n)) {
if (is != null)
return read(is);
}
}
return null;
}
private static byte[] byteBuffer(int maxBytes) {
if (BYTE_BUFFER_CACHE != null) {
byte[] x = BYTE_BUFFER_CACHE.get();
if (x == null) {
x = new byte[BUFF_SIZE];
BYTE_BUFFER_CACHE.set(x);
BYTE_BUFFER_CACHE_MISSES.incrementAndGet();
} else {
BYTE_BUFFER_CACHE_HITS.incrementAndGet();
}
return x;
}
return new byte[buffSize(maxBytes)];
}
private static char[] charBuffer(int maxChars) {
if (CHAR_BUFFER_CACHE != null) {
char[] x = CHAR_BUFFER_CACHE.get();
if (x == null) {
x = new char[BUFF_SIZE];
CHAR_BUFFER_CACHE.set(x);
CHAR_BUFFER_CACHE_MISSES.incrementAndGet();
} else {
CHAR_BUFFER_CACHE_HITS.incrementAndGet();
}
return x;
}
return new char[buffSize(maxChars)];
}
private static int buffSize(long max) {
return (max > 0 && max < BUFF_SIZE) ? (int)max : BUFF_SIZE;
}
}