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.FileOutputStream;
025    import java.io.IOException;
026    import java.io.InputStream;
027    import java.io.OutputStream;
028    import java.io.Reader;
029    import java.io.UnsupportedEncodingException;
030    import java.io.Writer;
031    import java.nio.channels.FileChannel;
032    import java.nio.charset.Charset;
033    import java.nio.charset.UnsupportedCharsetException;
034    
035    import org.apache.camel.Exchange;
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    /**
040     * IO helper class.
041     *
042     * @version 
043     */
044    public final class IOHelper {
045        
046        private static final transient Logger LOG = LoggerFactory.getLogger(IOHelper.class);
047        private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
048        private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
049    
050        private IOHelper() {
051            // Utility Class
052        }
053        
054        /**
055         * Use this function instead of new String(byte[]) to avoid surprises from non-standard default encodings.
056         */
057        public static String newStringFromBytes(byte[] bytes) {
058            try {
059                return new String(bytes, UTF8_CHARSET.name());
060            } catch (UnsupportedEncodingException e) {
061                throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
062            }
063        }
064    
065        /**
066         * Use this function instead of new String(byte[], int, int) 
067         * to avoid surprises from non-standard default encodings.
068         */
069        public static String newStringFromBytes(byte[] bytes, int start, int length) {
070            try {
071                return new String(bytes, start, length, UTF8_CHARSET.name());
072            } catch (UnsupportedEncodingException e) {
073                throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
074            }
075        }
076    
077        /**
078         * Wraps the passed <code>in</code> into a {@link BufferedInputStream}
079         * object and returns that. If the passed <code>in</code> is already an
080         * instance of {@link BufferedInputStream} returns the same passed
081         * <code>in</code> reference as is (avoiding double wrapping).
082         * 
083         * @param in the wrapee to be used for the buffering support
084         * @return the passed <code>in</code> decorated through a
085         *         {@link BufferedInputStream} object as wrapper
086         */
087        public static BufferedInputStream buffered(InputStream in) {
088            ObjectHelper.notNull(in, "in");
089            return (in instanceof BufferedInputStream) ? (BufferedInputStream)in : new BufferedInputStream(in);
090        }
091    
092        /**
093         * Wraps the passed <code>out</code> into a {@link BufferedOutputStream}
094         * object and returns that. If the passed <code>out</code> is already an
095         * instance of {@link BufferedOutputStream} returns the same passed
096         * <code>out</code> reference as is (avoiding double wrapping).
097         * 
098         * @param out the wrapee to be used for the buffering support
099         * @return the passed <code>out</code> decorated through a
100         *         {@link BufferedOutputStream} object as wrapper
101         */
102        public static BufferedOutputStream buffered(OutputStream out) {
103            ObjectHelper.notNull(out, "out");
104            return (out instanceof BufferedOutputStream) ? (BufferedOutputStream)out : new BufferedOutputStream(out);
105        }
106    
107        /**
108         * Wraps the passed <code>reader</code> into a {@link BufferedReader} object
109         * and returns that. If the passed <code>reader</code> is already an
110         * instance of {@link BufferedReader} returns the same passed
111         * <code>reader</code> reference as is (avoiding double wrapping).
112         * 
113         * @param reader the wrapee to be used for the buffering support
114         * @return the passed <code>reader</code> decorated through a
115         *         {@link BufferedReader} object as wrapper
116         */
117        public static BufferedReader buffered(Reader reader) {
118            ObjectHelper.notNull(reader, "reader");
119            return (reader instanceof BufferedReader) ? (BufferedReader)reader : new BufferedReader(reader);
120        }
121    
122        /**
123         * Wraps the passed <code>writer</code> into a {@link BufferedWriter} object
124         * and returns that. If the passed <code>writer</code> is already an
125         * instance of {@link BufferedWriter} returns the same passed
126         * <code>writer</code> reference as is (avoiding double wrapping).
127         * 
128         * @param writer the wrapee to be used for the buffering support
129         * @return the passed <code>writer</code> decorated through a
130         *         {@link BufferedWriter} object as wrapper
131         */
132        public static BufferedWriter buffered(Writer writer) {
133            ObjectHelper.notNull(writer, "writer");
134            return (writer instanceof BufferedWriter) ? (BufferedWriter)writer : new BufferedWriter(writer);
135        }
136    
137        /**
138         * A factory method which creates an {@link IOException} from the given
139         * exception and message
140         *
141         * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
142         */
143        @Deprecated
144        public static IOException createIOException(Throwable cause) {
145            return createIOException(cause.getMessage(), cause);
146        }
147    
148        /**
149         * A factory method which creates an {@link IOException} from the given
150         * exception and message
151         *
152         * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
153         */
154        @Deprecated
155        public static IOException createIOException(String message, Throwable cause) {
156            IOException answer = new IOException(message);
157            answer.initCause(cause);
158            return answer;
159        }
160    
161        public static int copy(InputStream input, OutputStream output) throws IOException {
162            return copy(input, output, DEFAULT_BUFFER_SIZE);
163        }
164        
165        public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
166            int avail = input.available();
167            if (avail > 262144) {
168                avail = 262144;
169            }
170            if (avail > bufferSize) {
171                bufferSize = avail;
172            }
173    
174            final byte[] buffer = new byte[bufferSize];
175            int n = input.read(buffer);
176            int total = 0;
177            while (-1 != n) {
178                output.write(buffer, 0, n);
179                total += n;
180                n = input.read(buffer);
181            }
182            output.flush();
183            return total;
184        }
185        
186        public static void copyAndCloseInput(InputStream input, OutputStream output) throws IOException {
187            copyAndCloseInput(input, output, DEFAULT_BUFFER_SIZE);
188        }
189        
190        public static void copyAndCloseInput(InputStream input, OutputStream output, int bufferSize) throws IOException {
191            copy(input, output, bufferSize);
192            close(input, null, LOG);
193        }
194    
195        public static int copy(final Reader input, final Writer output, int bufferSize) throws IOException {
196            final char[] buffer = new char[bufferSize];
197            int n = input.read(buffer);
198            int total = 0;
199            while (-1 != n) {
200                output.write(buffer, 0, n);
201                total += n;
202                n = input.read(buffer);
203            }
204            output.flush();
205            return total;
206        }
207    
208        /**
209         * Forces any updates to this channel's file to be written to the storage device that contains it.
210         *
211         * @param channel the file channel
212         * @param name the name of the resource
213         * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
214         */
215        public static void force(FileChannel channel, String name, Logger log) {
216            try {
217                if (channel != null) {
218                    channel.force(true);
219                }
220            } catch (Exception e) {
221                if (log == null) {
222                    // then fallback to use the own Logger
223                    log = LOG;
224                }
225                if (name != null) {
226                    log.warn("Cannot force FileChannel: " + name + ". Reason: " + e.getMessage(), e);
227                } else {
228                    log.warn("Cannot force FileChannel. Reason: " + e.getMessage(), e);
229                }
230            }
231        }
232    
233        /**
234         * Forces any updates to a FileOutputStream be written to the storage device that contains it.
235         *
236         * @param os the file output stream
237         * @param name the name of the resource
238         * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
239         */
240        public static void force(FileOutputStream os, String name, Logger log) {
241            try {
242                if (os != null) {
243                    os.getFD().sync();
244                }
245            } catch (Exception e) {
246                if (log == null) {
247                    // then fallback to use the own Logger
248                    log = LOG;
249                }
250                if (name != null) {
251                    log.warn("Cannot sync FileDescriptor: " + name + ". Reason: " + e.getMessage(), e);
252                } else {
253                    log.warn("Cannot sync FileDescriptor. Reason: " + e.getMessage(), e);
254                }
255            }
256        }
257    
258        /**
259         * Closes the given writer, logging any closing exceptions to the given log.
260         * An associated FileOutputStream can optionally be forced to disk.
261         *
262         * @param writer the writer to close
263         * @param os an underlying FileOutputStream that will to be forced to disk according to the the force parameter
264         * @param name the name of the resource
265         * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
266         * @param force forces the FileOutputStream to disk
267         */
268        public static void close(Writer writer, FileOutputStream os, String name, Logger log, boolean force) {
269            if (writer != null && force) {
270                // flush the writer prior to syncing the FD
271                try {
272                    writer.flush();
273                } catch (Exception e) {
274                    if (log == null) {
275                        // then fallback to use the own Logger
276                        log = LOG;
277                    }
278                    if (name != null) {
279                        log.warn("Cannot flush Writer: " + name + ". Reason: " + e.getMessage(), e);
280                    } else {
281                        log.warn("Cannot flush Writer. Reason: " + e.getMessage(), e);
282                    }
283                }
284                force(os, name, log);
285            }
286            close(writer, name, log);
287        }
288    
289        /**
290         * Closes the given resource if it is available, logging any closing exceptions to the given log.
291         *
292         * @param closeable the object to close
293         * @param name the name of the resource
294         * @param log the log to use when reporting closure warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
295         */
296        public static void close(Closeable closeable, String name, Logger log) {
297            if (closeable != null) {
298                try {
299                    closeable.close();
300                } catch (IOException e) {
301                    if (log == null) {
302                        // then fallback to use the own Logger
303                        log = LOG;
304                    }
305                    if (name != null) {
306                        log.warn("Cannot close: " + name + ". Reason: " + e.getMessage(), e);
307                    } else {
308                        log.warn("Cannot close. Reason: " + e.getMessage(), e);
309                    }
310                }
311            }
312        }
313    
314        /**
315         * Closes the given channel if it is available, logging any closing exceptions to the given log.
316         * The file's channel can optionally be forced to disk.
317         *
318         * @param channel the file channel
319         * @param name the name of the resource
320         * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
321         * @param force forces the file channel to disk
322         */
323        public static void close(FileChannel channel, String name, Logger log, boolean force) {
324            if (force) {
325                force(channel, name, log);
326            }
327            close(channel, name, log);
328        }
329    
330        /**
331         * Closes the given resource if it is available.
332         *
333         * @param closeable the object to close
334         * @param name the name of the resource
335         */
336        public static void close(Closeable closeable, String name) {
337            close(closeable, name, LOG);
338        }
339    
340        /**
341         * Closes the given resource if it is available.
342         *
343         * @param closeable the object to close
344         */
345        public static void close(Closeable closeable) {
346            close(closeable, null, LOG);
347        }
348    
349        /**
350         * Closes the given resources if they are available.
351         * 
352         * @param closeables the objects to close
353         */
354        public static void close(Closeable... closeables) {
355            for (Closeable closeable : closeables) {
356                close(closeable);
357            }
358        }
359    
360        public static void validateCharset(String charset) throws UnsupportedCharsetException {
361            if (charset != null) {
362                if (Charset.isSupported(charset)) {
363                    Charset.forName(charset);
364                    return;
365                }
366            }
367            throw new UnsupportedCharsetException(charset);
368        }
369    
370        /**
371         * This method will take off the quotes and double quotes of the charset
372         */
373        public static String normalizeCharset(String charset) {
374            if (charset != null) {
375                String answer = charset.trim();
376                if (answer.startsWith("'") || answer.startsWith("\"")) {
377                    answer = answer.substring(1);
378                }
379                if (answer.endsWith("'") || answer.endsWith("\"")) {
380                    answer = answer.substring(0, answer.length() - 1);
381                }
382                return answer.trim();
383            } else {
384                return null;
385            }
386        }
387    
388        public static String getCharsetName(Exchange exchange) {
389            return getCharsetName(exchange, true);
390        }
391    
392        /**
393         * Gets the charset name if set as property {@link Exchange#CHARSET_NAME}.
394         *
395         * @param exchange  the exchange
396         * @param useDefault should we fallback and use JVM default charset if no property existed?
397         * @return the charset, or <tt>null</tt> if no found
398         */
399        public static String getCharsetName(Exchange exchange, boolean useDefault) {
400            if (exchange != null) {
401                String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
402                if (charsetName != null) {
403                    return IOHelper.normalizeCharset(charsetName);
404                }
405            }
406            if (useDefault) {
407                return getDefaultCharsetName();
408            } else {
409                return null;
410            }
411        }
412        
413        private static String getDefaultCharsetName() {
414            return ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8");
415        }
416    }