/**
* 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.camel.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import org.apache.camel.Exchange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* IO helper class.
*
* @version
*/
public final class IOHelper {
public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private static final Logger LOG = LoggerFactory.getLogger(IOHelper.class);
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private IOHelper() {
// Utility Class
}
/**
* Use this function instead of new String(byte[]) to avoid surprises from non-standard default encodings.
*/
public static String newStringFromBytes(byte[] bytes) {
try {
return new String(bytes, UTF8_CHARSET.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
}
}
/**
* Use this function instead of new String(byte[], int, int)
* to avoid surprises from non-standard default encodings.
*/
public static String newStringFromBytes(byte[] bytes, int start, int length) {
try {
return new String(bytes, start, length, UTF8_CHARSET.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
}
}
/**
* Wraps the passed <code>in</code> into a {@link BufferedInputStream}
* object and returns that. If the passed <code>in</code> is already an
* instance of {@link BufferedInputStream} returns the same passed
* <code>in</code> reference as is (avoiding double wrapping).
*
* @param in the wrapee to be used for the buffering support
* @return the passed <code>in</code> decorated through a
* {@link BufferedInputStream} object as wrapper
*/
public static BufferedInputStream buffered(InputStream in) {
ObjectHelper.notNull(in, "in");
return (in instanceof BufferedInputStream) ? (BufferedInputStream)in : new BufferedInputStream(in);
}
/**
* Wraps the passed <code>out</code> into a {@link BufferedOutputStream}
* object and returns that. If the passed <code>out</code> is already an
* instance of {@link BufferedOutputStream} returns the same passed
* <code>out</code> reference as is (avoiding double wrapping).
*
* @param out the wrapee to be used for the buffering support
* @return the passed <code>out</code> decorated through a
* {@link BufferedOutputStream} object as wrapper
*/
public static BufferedOutputStream buffered(OutputStream out) {
ObjectHelper.notNull(out, "out");
return (out instanceof BufferedOutputStream) ? (BufferedOutputStream)out : new BufferedOutputStream(out);
}
/**
* Wraps the passed <code>reader</code> into a {@link BufferedReader} object
* and returns that. If the passed <code>reader</code> is already an
* instance of {@link BufferedReader} returns the same passed
* <code>reader</code> reference as is (avoiding double wrapping).
*
* @param reader the wrapee to be used for the buffering support
* @return the passed <code>reader</code> decorated through a
* {@link BufferedReader} object as wrapper
*/
public static BufferedReader buffered(Reader reader) {
ObjectHelper.notNull(reader, "reader");
return (reader instanceof BufferedReader) ? (BufferedReader)reader : new BufferedReader(reader);
}
/**
* Wraps the passed <code>writer</code> into a {@link BufferedWriter} object
* and returns that. If the passed <code>writer</code> is already an
* instance of {@link BufferedWriter} returns the same passed
* <code>writer</code> reference as is (avoiding double wrapping).
*
* @param writer the wrapee to be used for the buffering support
* @return the passed <code>writer</code> decorated through a
* {@link BufferedWriter} object as wrapper
*/
public static BufferedWriter buffered(Writer writer) {
ObjectHelper.notNull(writer, "writer");
return (writer instanceof BufferedWriter) ? (BufferedWriter)writer : new BufferedWriter(writer);
}
/**
* A factory method which creates an {@link IOException} from the given
* exception and message
*
* @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
*/
@Deprecated
public static IOException createIOException(Throwable cause) {
return createIOException(cause.getMessage(), cause);
}
/**
* A factory method which creates an {@link IOException} from the given
* exception and message
*
* @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
*/
@Deprecated
public static IOException createIOException(String message, Throwable cause) {
IOException answer = new IOException(message);
answer.initCause(cause);
return answer;
}
public static int copy(InputStream input, OutputStream output) throws IOException {
return copy(input, output, DEFAULT_BUFFER_SIZE);
}
public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
return copy(input, output, bufferSize, false);
}
public static int copy(final InputStream input, final OutputStream output, int bufferSize, boolean flushOnEachWrite) throws IOException {
if (input instanceof ByteArrayInputStream) {
// optimized for byte array as we only need the max size it can be
input.mark(0);
input.reset();
bufferSize = input.available();
} else {
int avail = input.available();
if (avail > bufferSize) {
bufferSize = avail;
}
}
if (bufferSize > 262144) {
// upper cap to avoid buffers too big
bufferSize = 262144;
}
if (LOG.isTraceEnabled()) {
LOG.trace("Copying InputStream: {} -> OutputStream: {} with buffer: {} and flush on each write {}",
new Object[]{input, output, bufferSize, flushOnEachWrite});
}
final byte[] buffer = new byte[bufferSize];
int n = input.read(buffer);
int total = 0;
while (-1 != n) {
output.write(buffer, 0, n);
if (flushOnEachWrite) {
output.flush();
}
total += n;
n = input.read(buffer);
}
if (!flushOnEachWrite) {
// flush at end, if we didn't do it during the writing
output.flush();
}
return total;
}
public static void copyAndCloseInput(InputStream input, OutputStream output) throws IOException {
copyAndCloseInput(input, output, DEFAULT_BUFFER_SIZE);
}
public static void copyAndCloseInput(InputStream input, OutputStream output, int bufferSize) throws IOException {
copy(input, output, bufferSize);
close(input, null, LOG);
}
public static int copy(final Reader input, final Writer output, int bufferSize) throws IOException {
final char[] buffer = new char[bufferSize];
int n = input.read(buffer);
int total = 0;
while (-1 != n) {
output.write(buffer, 0, n);
total += n;
n = input.read(buffer);
}
output.flush();
return total;
}
/**
* Forces any updates to this channel's file to be written to the storage device that contains it.
*
* @param channel the file channel
* @param name the name of the resource
* @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
*/
public static void force(FileChannel channel, String name, Logger log) {
try {
if (channel != null) {
channel.force(true);
}
} catch (Exception e) {
if (log == null) {
// then fallback to use the own Logger
log = LOG;
}
if (name != null) {
log.warn("Cannot force FileChannel: " + name + ". Reason: " + e.getMessage(), e);
} else {
log.warn("Cannot force FileChannel. Reason: " + e.getMessage(), e);
}
}
}
/**
* Forces any updates to a FileOutputStream be written to the storage device that contains it.
*
* @param os the file output stream
* @param name the name of the resource
* @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
*/
public static void force(FileOutputStream os, String name, Logger log) {
try {
if (os != null) {
os.getFD().sync();
}
} catch (Exception e) {
if (log == null) {
// then fallback to use the own Logger
log = LOG;
}
if (name != null) {
log.warn("Cannot sync FileDescriptor: " + name + ". Reason: " + e.getMessage(), e);
} else {
log.warn("Cannot sync FileDescriptor. Reason: " + e.getMessage(), e);
}
}
}
/**
* Closes the given writer, logging any closing exceptions to the given log.
* An associated FileOutputStream can optionally be forced to disk.
*
* @param writer the writer to close
* @param os an underlying FileOutputStream that will to be forced to disk according to the the force parameter
* @param name the name of the resource
* @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
* @param force forces the FileOutputStream to disk
*/
public static void close(Writer writer, FileOutputStream os, String name, Logger log, boolean force) {
if (writer != null && force) {
// flush the writer prior to syncing the FD
try {
writer.flush();
} catch (Exception e) {
if (log == null) {
// then fallback to use the own Logger
log = LOG;
}
if (name != null) {
log.warn("Cannot flush Writer: " + name + ". Reason: " + e.getMessage(), e);
} else {
log.warn("Cannot flush Writer. Reason: " + e.getMessage(), e);
}
}
force(os, name, log);
}
close(writer, name, log);
}
/**
* Closes the given resource if it is available, logging any closing exceptions to the given log.
*
* @param closeable the object to close
* @param name the name of the resource
* @param log the log to use when reporting closure warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
*/
public static void close(Closeable closeable, String name, Logger log) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
if (log == null) {
// then fallback to use the own Logger
log = LOG;
}
if (name != null) {
log.warn("Cannot close: " + name + ". Reason: " + e.getMessage(), e);
} else {
log.warn("Cannot close. Reason: " + e.getMessage(), e);
}
}
}
}
/**
* Closes the given resource if it is available and don't catch the exception
*
* @param closeable the object to close
* @throws IOException
*/
public static void closeWithException(Closeable closeable) throws IOException {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
// don't catch the exception here
throw e;
}
}
}
/**
* Closes the given channel if it is available, logging any closing exceptions to the given log.
* The file's channel can optionally be forced to disk.
*
* @param channel the file channel
* @param name the name of the resource
* @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
* @param force forces the file channel to disk
*/
public static void close(FileChannel channel, String name, Logger log, boolean force) {
if (force) {
force(channel, name, log);
}
close(channel, name, log);
}
/**
* Closes the given resource if it is available.
*
* @param closeable the object to close
* @param name the name of the resource
*/
public static void close(Closeable closeable, String name) {
close(closeable, name, LOG);
}
/**
* Closes the given resource if it is available.
*
* @param closeable the object to close
*/
public static void close(Closeable closeable) {
close(closeable, null, LOG);
}
/**
* Closes the given resources if they are available.
*
* @param closeables the objects to close
*/
public static void close(Closeable... closeables) {
for (Closeable closeable : closeables) {
close(closeable);
}
}
public static void validateCharset(String charset) throws UnsupportedCharsetException {
if (charset != null) {
if (Charset.isSupported(charset)) {
Charset.forName(charset);
return;
}
}
throw new UnsupportedCharsetException(charset);
}
/**
* This method will take off the quotes and double quotes of the charset
*/
public static String normalizeCharset(String charset) {
if (charset != null) {
String answer = charset.trim();
if (answer.startsWith("'") || answer.startsWith("\"")) {
answer = answer.substring(1);
}
if (answer.endsWith("'") || answer.endsWith("\"")) {
answer = answer.substring(0, answer.length() - 1);
}
return answer.trim();
} else {
return null;
}
}
/**
* @see #getCharsetName(org.apache.camel.Exchange, boolean)
*/
public static String getCharsetName(Exchange exchange) {
return getCharsetName(exchange, true);
}
/**
* Gets the charset name if set as header or property {@link Exchange#CHARSET_NAME}.
* <b>Notice:</b> The lookup from the header has priority over the property.
*
* @param exchange the exchange
* @param useDefault should we fallback and use JVM default charset if no property existed?
* @return the charset, or <tt>null</tt> if no found
*/
public static String getCharsetName(Exchange exchange, boolean useDefault) {
if (exchange != null) {
// header takes precedence
String charsetName = exchange.getIn().getHeader(Exchange.CHARSET_NAME, String.class);
if (charsetName == null) {
charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
}
if (charsetName != null) {
return IOHelper.normalizeCharset(charsetName);
}
}
if (useDefault) {
return getDefaultCharsetName();
} else {
return null;
}
}
private static String getDefaultCharsetName() {
return ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8");
}
/**
* Loads the entire stream into memory as a String and returns it.
* <p/>
* <b>Notice:</b> This implementation appends a <tt>\n</tt> as line
* terminator at the of the text.
* <p/>
* Warning, don't use for crazy big streams :)
*/
public static String loadText(InputStream in) throws IOException {
StringBuilder builder = new StringBuilder();
InputStreamReader isr = new InputStreamReader(in);
try {
BufferedReader reader = buffered(isr);
while (true) {
String line = reader.readLine();
if (line != null) {
builder.append(line);
builder.append("\n");
} else {
break;
}
}
return builder.toString();
} finally {
close(isr, in);
}
}
/**
* Get the charset name from the content type string
* @param contentType
* @return the charset name, or <tt>UTF-8</tt> if no found
*/
public static String getCharsetNameFromContentType(String contentType) {
String[] values = contentType.split(";");
String charset = "";
for (String value : values) {
value = value.trim();
if (value.toLowerCase().startsWith("charset=")) {
// Take the charset name
charset = value.substring(8);
}
}
if ("".equals(charset)) {
charset = "UTF-8";
}
return IOHelper.normalizeCharset(charset);
}
}