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    }