001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.util;
018
019 import java.io.BufferedInputStream;
020 import java.io.BufferedOutputStream;
021 import java.io.BufferedReader;
022 import java.io.BufferedWriter;
023 import java.io.Closeable;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.OutputStream;
027 import java.io.Reader;
028 import java.io.UnsupportedEncodingException;
029 import java.io.Writer;
030 import java.nio.channels.FileChannel;
031 import java.nio.charset.Charset;
032 import java.nio.charset.UnsupportedCharsetException;
033
034 import org.apache.camel.Exchange;
035 import org.slf4j.Logger;
036 import org.slf4j.LoggerFactory;
037
038 /**
039 * IO helper class.
040 *
041 * @version
042 */
043 public final class IOHelper {
044
045 private static final transient Logger LOG = LoggerFactory.getLogger(IOHelper.class);
046 private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
047 private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
048
049 private IOHelper() {
050 // Utility Class
051 }
052
053 /**
054 * Use this function instead of new String(byte[]) to avoid surprises from non-standard default encodings.
055 */
056 public static String newStringFromBytes(byte[] bytes) {
057 try {
058 return new String(bytes, UTF8_CHARSET.name());
059 } catch (UnsupportedEncodingException e) {
060 throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
061 }
062 }
063
064 /**
065 * Use this function instead of new String(byte[], int, int)
066 * to avoid surprises from non-standard default encodings.
067 */
068 public static String newStringFromBytes(byte[] bytes, int start, int length) {
069 try {
070 return new String(bytes, start, length, UTF8_CHARSET.name());
071 } catch (UnsupportedEncodingException e) {
072 throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
073 }
074 }
075
076 /**
077 * Wraps the passed <code>in</code> into a {@link BufferedInputStream}
078 * object and returns that. If the passed <code>in</code> is already an
079 * instance of {@link BufferedInputStream} returns the same passed
080 * <code>in</code> reference as is (avoiding double wrapping).
081 *
082 * @param in the wrapee to be used for the buffering support
083 * @return the passed <code>in</code> decorated through a
084 * {@link BufferedInputStream} object as wrapper
085 */
086 public static BufferedInputStream buffered(InputStream in) {
087 ObjectHelper.notNull(in, "in");
088 return (in instanceof BufferedInputStream) ? (BufferedInputStream)in : new BufferedInputStream(in);
089 }
090
091 /**
092 * Wraps the passed <code>out</code> into a {@link BufferedOutputStream}
093 * object and returns that. If the passed <code>out</code> is already an
094 * instance of {@link BufferedOutputStream} returns the same passed
095 * <code>out</code> reference as is (avoiding double wrapping).
096 *
097 * @param out the wrapee to be used for the buffering support
098 * @return the passed <code>out</code> decorated through a
099 * {@link BufferedOutputStream} object as wrapper
100 */
101 public static BufferedOutputStream buffered(OutputStream out) {
102 ObjectHelper.notNull(out, "out");
103 return (out instanceof BufferedOutputStream) ? (BufferedOutputStream)out : new BufferedOutputStream(out);
104 }
105
106 /**
107 * Wraps the passed <code>reader</code> into a {@link BufferedReader} object
108 * and returns that. If the passed <code>reader</code> is already an
109 * instance of {@link BufferedReader} returns the same passed
110 * <code>reader</code> reference as is (avoiding double wrapping).
111 *
112 * @param reader the wrapee to be used for the buffering support
113 * @return the passed <code>reader</code> decorated through a
114 * {@link BufferedReader} object as wrapper
115 */
116 public static BufferedReader buffered(Reader reader) {
117 ObjectHelper.notNull(reader, "reader");
118 return (reader instanceof BufferedReader) ? (BufferedReader)reader : new BufferedReader(reader);
119 }
120
121 /**
122 * Wraps the passed <code>writer</code> into a {@link BufferedWriter} object
123 * and returns that. If the passed <code>writer</code> is already an
124 * instance of {@link BufferedWriter} returns the same passed
125 * <code>writer</code> reference as is (avoiding double wrapping).
126 *
127 * @param writer the wrapee to be used for the buffering support
128 * @return the passed <code>writer</code> decorated through a
129 * {@link BufferedWriter} object as wrapper
130 */
131 public static BufferedWriter buffered(Writer writer) {
132 ObjectHelper.notNull(writer, "writer");
133 return (writer instanceof BufferedWriter) ? (BufferedWriter)writer : new BufferedWriter(writer);
134 }
135
136 /**
137 * A factory method which creates an {@link IOException} from the given
138 * exception and message
139 *
140 * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
141 */
142 @Deprecated
143 public static IOException createIOException(Throwable cause) {
144 return createIOException(cause.getMessage(), cause);
145 }
146
147 /**
148 * A factory method which creates an {@link IOException} from the given
149 * exception and message
150 *
151 * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
152 */
153 @Deprecated
154 public static IOException createIOException(String message, Throwable cause) {
155 IOException answer = new IOException(message);
156 answer.initCause(cause);
157 return answer;
158 }
159
160 public static int copy(InputStream input, OutputStream output) throws IOException {
161 return copy(input, output, DEFAULT_BUFFER_SIZE);
162 }
163
164 public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
165 int avail = input.available();
166 if (avail > 262144) {
167 avail = 262144;
168 }
169 if (avail > bufferSize) {
170 bufferSize = avail;
171 }
172
173 final byte[] buffer = new byte[bufferSize];
174 int n = input.read(buffer);
175 int total = 0;
176 while (-1 != n) {
177 output.write(buffer, 0, n);
178 total += n;
179 n = input.read(buffer);
180 }
181 output.flush();
182 return total;
183 }
184
185 public static void copyAndCloseInput(InputStream input, OutputStream output) throws IOException {
186 copyAndCloseInput(input, output, DEFAULT_BUFFER_SIZE);
187 }
188
189 public static void copyAndCloseInput(InputStream input, OutputStream output, int bufferSize) throws IOException {
190 copy(input, output, bufferSize);
191 close(input, null, LOG);
192 }
193
194 public static int copy(final Reader input, final Writer output, int bufferSize) throws IOException {
195 final char[] buffer = new char[bufferSize];
196 int n = input.read(buffer);
197 int total = 0;
198 while (-1 != n) {
199 output.write(buffer, 0, n);
200 total += n;
201 n = input.read(buffer);
202 }
203 output.flush();
204 return total;
205 }
206
207 /**
208 * Forces any updates to this channel's file to be written to the storage device that contains it.
209 *
210 * @param channel the file channel
211 * @param name the name of the resource
212 * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
213 */
214 public static void force(FileChannel channel, String name, Logger log) {
215 try {
216 if (channel != null) {
217 channel.force(true);
218 }
219 } catch (Exception e) {
220 if (log == null) {
221 // then fallback to use the own Logger
222 log = LOG;
223 }
224 if (name != null) {
225 log.warn("Cannot force FileChannel: " + name + ". Reason: " + e.getMessage(), e);
226 } else {
227 log.warn("Cannot force FileChannel. Reason: " + e.getMessage(), e);
228 }
229 }
230 }
231
232 /**
233 * Closes the given resource if it is available, logging any closing exceptions to the given log.
234 *
235 * @param closeable the object to close
236 * @param name the name of the resource
237 * @param log the log to use when reporting closure warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
238 */
239 public static void close(Closeable closeable, String name, Logger log) {
240 if (closeable != null) {
241 try {
242 closeable.close();
243 } catch (IOException e) {
244 if (log == null) {
245 // then fallback to use the own Logger
246 log = LOG;
247 }
248 if (name != null) {
249 log.warn("Cannot close: " + name + ". Reason: " + e.getMessage(), e);
250 } else {
251 log.warn("Cannot close. Reason: " + e.getMessage(), e);
252 }
253 }
254 }
255 }
256
257 /**
258 * Closes the given resource if it is available.
259 *
260 * @param closeable the object to close
261 * @param name the name of the resource
262 */
263 public static void close(Closeable closeable, String name) {
264 close(closeable, name, LOG);
265 }
266
267 /**
268 * Closes the given resource if it is available.
269 *
270 * @param closeable the object to close
271 */
272 public static void close(Closeable closeable) {
273 close(closeable, null, LOG);
274 }
275
276 /**
277 * Closes the given resources if they are available.
278 *
279 * @param closeables the objects to close
280 */
281 public static void close(Closeable... closeables) {
282 for (Closeable closeable : closeables) {
283 close(closeable);
284 }
285 }
286
287 public static void validateCharset(String charset) throws UnsupportedCharsetException {
288 if (charset != null) {
289 if (Charset.isSupported(charset)) {
290 Charset.forName(charset);
291 return;
292 }
293 }
294 throw new UnsupportedCharsetException(charset);
295 }
296
297 /**
298 * This method will take off the quotes and double quotes of the charset
299 */
300 public static String normalizeCharset(String charset) {
301 if (charset != null) {
302 String answer = charset.trim();
303 if (answer.startsWith("'") || answer.startsWith("\"")) {
304 answer = answer.substring(1);
305 }
306 if (answer.endsWith("'") || answer.endsWith("\"")) {
307 answer = answer.substring(0, answer.length() - 1);
308 }
309 return answer.trim();
310 } else {
311 return null;
312 }
313 }
314
315 public static String getCharsetName(Exchange exchange) {
316 return getCharsetName(exchange, true);
317 }
318
319 /**
320 * Gets the charset name if set as property {@link Exchange#CHARSET_NAME}.
321 *
322 * @param exchange the exchange
323 * @param useDefault should we fallback and use JVM default charset if no property existed?
324 * @return the charset, or <tt>null</tt> if no found
325 */
326 public static String getCharsetName(Exchange exchange, boolean useDefault) {
327 if (exchange != null) {
328 String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
329 if (charsetName != null) {
330 return IOHelper.normalizeCharset(charsetName);
331 }
332 }
333 if (useDefault) {
334 return getDefaultCharsetName();
335 } else {
336 return null;
337 }
338 }
339
340 private static String getDefaultCharsetName() {
341 return ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8");
342 }
343 }