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 */
017package org.apache.camel.converter;
018
019import java.io.BufferedReader;
020import java.io.BufferedWriter;
021import java.io.ByteArrayInputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileNotFoundException;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.InputStreamReader;
030import java.io.ObjectInput;
031import java.io.ObjectInputStream;
032import java.io.ObjectOutput;
033import java.io.ObjectOutputStream;
034import java.io.ObjectStreamClass;
035import java.io.OutputStream;
036import java.io.OutputStreamWriter;
037import java.io.Reader;
038import java.io.StringReader;
039import java.io.UnsupportedEncodingException;
040import java.io.Writer;
041import java.net.URL;
042import java.nio.ByteBuffer;
043import java.nio.CharBuffer;
044import java.nio.charset.Charset;
045import java.nio.charset.CharsetEncoder;
046import java.nio.charset.UnsupportedCharsetException;
047import java.util.Properties;
048
049import org.apache.camel.Converter;
050import org.apache.camel.Exchange;
051import org.apache.camel.util.IOHelper;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055/**
056 * Some core java.io based <a
057 * href="http://camel.apache.org/type-converter.html">Type Converters</a>
058 *
059 * @version 
060 */
061@Converter
062public final class IOConverter {
063
064    private static final Logger LOG = LoggerFactory.getLogger(IOConverter.class);
065
066    /**
067     * Utility classes should not have a public constructor.
068     */
069    private IOConverter() {
070    }
071
072    @Converter
073    public static InputStream toInputStream(URL url) throws IOException {
074        return IOHelper.buffered(url.openStream());
075    }
076
077    @Converter
078    public static InputStream toInputStream(File file) throws IOException {
079        return IOHelper.buffered(new FileInputStream(file));
080    }
081
082    /**
083     * Converts the given {@link File} with the given charset to {@link InputStream} with the JVM default charset
084     *
085     * @param file the file to be converted
086     * @param charset the charset the file is read with
087     * @return the input stream with the JVM default charset
088     */
089    public static InputStream toInputStream(File file, String charset) throws IOException {
090        if (charset != null) {
091            return new EncodingInputStream(file, charset);
092        } else {
093            return toInputStream(file);
094        }
095    }
096
097    /**
098     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
099     */
100    @Deprecated
101    public static BufferedReader toReader(File file) throws IOException {
102        return toReader(file, (String) null);
103    }
104
105    @Converter
106    public static BufferedReader toReader(File file, Exchange exchange) throws IOException {
107        return toReader(file, IOHelper.getCharsetName(exchange));
108    }
109
110    public static BufferedReader toReader(File file, String charset) throws IOException {
111        FileInputStream in = new FileInputStream(file);
112        return IOHelper.buffered(new EncodingFileReader(in, charset));
113    }
114
115    @Converter
116    public static File toFile(String name) {
117        return new File(name);
118    }
119
120    @Converter
121    public static OutputStream toOutputStream(File file) throws FileNotFoundException {
122        return IOHelper.buffered(new FileOutputStream(file));
123    }
124
125    /**
126     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
127     */
128    @Deprecated
129    public static BufferedWriter toWriter(File file) throws IOException {
130        FileOutputStream os = new FileOutputStream(file, false);
131        return toWriter(os, IOHelper.getCharsetName(null, true));
132    }
133    
134    @Converter
135    public static BufferedWriter toWriter(File file, Exchange exchange) throws IOException {
136        FileOutputStream os = new FileOutputStream(file, false);
137        return toWriter(os, IOHelper.getCharsetName(exchange));
138    }
139
140    public static BufferedWriter toWriter(File file, boolean append, String charset) throws IOException {
141        return toWriter(new FileOutputStream(file, append), charset);
142    }
143
144    public static BufferedWriter toWriter(FileOutputStream os, String charset) throws IOException {
145        return IOHelper.buffered(new EncodingFileWriter(os, charset));
146    }
147    
148    public static BufferedWriter toWriter(FileOutputStream os, CharsetEncoder enc) throws IOException {
149        return IOHelper.buffered(new EncodingFileWriter(os, enc));
150    }
151
152    /**
153     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
154     */
155    @Deprecated
156    public static Reader toReader(InputStream in) throws IOException {
157        return toReader(in, null);
158    }
159
160    @Converter
161    public static Reader toReader(InputStream in, Exchange exchange) throws IOException {
162        return IOHelper.buffered(new InputStreamReader(in, IOHelper.getCharsetName(exchange)));
163    }
164
165    /**
166     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
167     */
168    @Deprecated
169    public static Writer toWriter(OutputStream out) throws IOException {
170        return toWriter(out, null);
171    }
172    
173    @Converter
174    public static Writer toWriter(OutputStream out, Exchange exchange) throws IOException {
175        return IOHelper.buffered(new OutputStreamWriter(out, IOHelper.getCharsetName(exchange)));
176    }
177
178    @Converter
179    public static StringReader toReader(String text) {
180        // no buffering required as the complete string input is already passed
181        // over as a whole
182        return new StringReader(text);
183    }
184
185    /**
186     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
187     */
188    @Deprecated
189    public static InputStream toInputStream(String text) throws IOException {
190        return toInputStream(text, null);
191    }
192    
193    @Converter
194    public static InputStream toInputStream(String text, Exchange exchange) throws IOException {
195        return toInputStream(text.getBytes(IOHelper.getCharsetName(exchange)));
196    }
197    
198    @Converter
199    public static InputStream toInputStream(StringBuffer buffer, Exchange exchange) throws IOException {
200        return toInputStream(buffer.toString(), exchange);
201    }
202    
203    @Converter
204    public static InputStream toInputStream(StringBuilder builder, Exchange exchange) throws IOException {
205        return toInputStream(builder.toString(), exchange);
206    }
207    
208    /**
209     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
210     */
211    @Deprecated
212    public static InputStream toInputStream(BufferedReader buffer) throws IOException {
213        return toInputStream(buffer, null);
214    }
215    
216    @Converter
217    public static InputStream toInputStream(BufferedReader buffer, Exchange exchange) throws IOException {
218        return toInputStream(toString(buffer), exchange);
219    }
220
221    /**
222     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
223     */
224    @Deprecated
225    public static String toString(byte[] data) throws IOException {
226        return toString(data, null);
227    }
228    
229    @Converter
230    public static String toString(byte[] data, Exchange exchange) throws IOException {
231        return new String(data, IOHelper.getCharsetName(exchange));
232    }
233
234    /**
235     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
236     */
237    @Deprecated
238    public static String toString(File file) throws IOException {
239        return toString(file, null);
240    }
241    
242    @Converter
243    public static String toString(File file, Exchange exchange) throws IOException {
244        return toString(toReader(file, exchange));
245    }
246
247    @Converter
248    public static byte[] toByteArray(File file) throws IOException {
249        InputStream is = toInputStream(file);
250        try {
251            return toBytes(is);
252        } finally {
253            IOHelper.close(is, "file", LOG);
254        }
255    }
256    
257    /**
258     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
259     */
260    @Deprecated
261    public static byte[] toByteArray(Reader reader) throws IOException {
262        return toByteArray(reader, null);
263    }
264    
265    @Converter
266    public static byte[] toByteArray(Reader reader, Exchange exchange) throws IOException {
267        return toByteArray(IOHelper.buffered(reader), exchange);
268    }
269
270    /**
271     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
272     */
273    @Deprecated
274    public static String toString(URL url) throws IOException {
275        return toString(url, null);
276    }
277
278    @Converter
279    public static String toString(URL url, Exchange exchange) throws IOException {
280        InputStream is = toInputStream(url);
281        try {
282            return toString(is, exchange);
283        } finally {
284            IOHelper.close(is, "url", LOG);
285        }
286    }
287
288    @Converter
289    public static String toString(Reader reader) throws IOException {
290        return toString(IOHelper.buffered(reader));
291    }
292
293    @Converter
294    public static String toString(BufferedReader reader) throws IOException {
295        StringBuilder sb = new StringBuilder(1024);
296        char[] buf = new char[1024];
297        try {
298            int len;
299            // read until we reach then end which is the -1 marker
300            while ((len = reader.read(buf)) != -1) {
301                sb.append(buf, 0, len);
302            }
303        } finally {
304            IOHelper.close(reader, "reader", LOG);
305        }
306
307        return sb.toString();
308    }
309    
310    /**
311     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
312     */
313    @Deprecated
314    public static byte[] toByteArray(BufferedReader reader) throws IOException {
315        return toByteArray(reader, null);
316    }
317    
318    @Converter
319    public static byte[] toByteArray(BufferedReader reader, Exchange exchange) throws IOException {
320        String s = toString(reader);
321        return toByteArray(s, exchange);
322    }
323
324    /**
325     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
326     */
327    @Deprecated
328    public static byte[] toByteArray(String value) throws IOException {
329        return toByteArray(value, null);
330    }
331
332    @Converter
333    public static byte[] toByteArray(String value, Exchange exchange) throws IOException {
334        return value.getBytes(IOHelper.getCharsetName(exchange));
335    }
336
337    /**
338     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
339     */
340    @Deprecated
341    public static String toString(InputStream in) throws IOException {
342        return toString(in, null);
343    }
344
345    @Converter
346    public static String toString(InputStream in, Exchange exchange) throws IOException {
347        return toString(toReader(in, exchange));
348    }
349
350    @Converter
351    public static InputStream toInputStream(byte[] data) {
352        // no buffering required as the complete byte input is already passed
353        // over as a whole
354        return new ByteArrayInputStream(data);
355    }
356
357    @Converter
358    public static ObjectOutput toObjectOutput(OutputStream stream) throws IOException {
359        if (stream instanceof ObjectOutput) {
360            return (ObjectOutput) stream;
361        } else {
362            return new ObjectOutputStream(IOHelper.buffered(stream));
363        }
364    }
365
366    @Converter
367    public static ObjectInput toObjectInput(final InputStream stream, final Exchange exchange) throws IOException {
368        if (stream instanceof ObjectInput) {
369            return (ObjectInput) stream;
370        } else {
371            return new ObjectInputStream(IOHelper.buffered(stream)) {
372                @Override
373                protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
374                    // need to let Camel be able to resolve class using ClassResolver SPI, to let class loading
375                    // work in OSGi and other containers
376                    Class<?>  answer = null;
377                    String name = objectStreamClass.getName();
378                    if (exchange != null) {
379                        LOG.trace("Loading class {} using Camel ClassResolver", name);
380                        answer = exchange.getContext().getClassResolver().resolveClass(name);
381                    }
382                    if (answer == null) {
383                        LOG.trace("Loading class {} using JDK default implementation", name);
384                        answer = super.resolveClass(objectStreamClass);
385                    }
386                    return answer;
387                }
388            };
389        }
390    }
391
392    @Converter
393    public static byte[] toBytes(InputStream stream) throws IOException {
394        ByteArrayOutputStream bos = new ByteArrayOutputStream();
395        IOHelper.copy(IOHelper.buffered(stream), bos);
396
397        // no need to close the ByteArrayOutputStream as it's close()
398        // implementation is noop
399        return bos.toByteArray();
400    }
401
402    @Converter
403    public static byte[] toByteArray(ByteArrayOutputStream os) {
404        return os.toByteArray();
405    }
406
407    /**
408     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
409     */
410    @Deprecated
411    public static String toString(ByteArrayOutputStream os) throws IOException {
412        return toString(os, null);
413    }
414
415    @Converter
416    public static String toString(ByteArrayOutputStream os, Exchange exchange) throws IOException {
417        return os.toString(IOHelper.getCharsetName(exchange));
418    }
419
420    @Converter
421    public static InputStream toInputStream(ByteArrayOutputStream os) {
422        // no buffering required as the complete byte array input is already
423        // passed over as a whole
424        return new ByteArrayInputStream(os.toByteArray());
425    }
426
427    @Converter
428    public static Properties toProperties(File file) throws IOException {
429        return toProperties(new FileInputStream(file));
430    }
431
432    @Converter
433    public static Properties toProperties(InputStream is) throws IOException {
434        Properties prop = new Properties();
435        try {
436            prop.load(is);
437        } finally {
438            IOHelper.close(is);
439        }
440        return prop;
441    }
442
443    @Converter
444    public static Properties toProperties(Reader reader) throws IOException {
445        Properties prop = new Properties();
446        try {
447            prop.load(reader);
448        } finally {
449            IOHelper.close(reader);
450        }
451        return prop;
452    }
453
454    /**
455     * Gets the charset name if set as header or property {@link Exchange#CHARSET_NAME}.
456     *
457     * @param exchange  the exchange
458     * @param useDefault should we fallback and use JVM default charset if no property existed?
459     * @return the charset, or <tt>null</tt> if no found
460     */
461    @Deprecated
462    public static String getCharsetName(Exchange exchange, boolean useDefault) {
463        return IOHelper.getCharsetName(exchange, useDefault);
464    }
465    
466    @Deprecated
467    public static String getCharsetName(Exchange exchange) {
468        return getCharsetName(exchange, true);
469    }
470
471    /**
472     * Encoding-aware input stream.
473     */
474    public static class EncodingInputStream extends InputStream {
475
476        private final File file;
477        private final BufferedReader reader;
478        private final Charset defaultStreamCharset;
479
480        private ByteBuffer bufferBytes;
481        private CharBuffer bufferedChars = CharBuffer.allocate(4096);
482
483        public EncodingInputStream(File file, String charset) throws IOException {
484            this.file = file;
485            reader = toReader(file, charset);
486            defaultStreamCharset = Charset.defaultCharset();
487        }
488
489        @Override
490        public int read() throws IOException {
491            if (bufferBytes == null || bufferBytes.remaining() <= 0) {
492                bufferedChars.clear();
493                int len = reader.read(bufferedChars);
494                bufferedChars.flip();
495                if (len == -1) {
496                    return -1;
497                }
498                bufferBytes = defaultStreamCharset.encode(bufferedChars);
499            }
500            return bufferBytes.get();
501        }
502
503        @Override
504        public void close() throws IOException {
505            reader.close();
506        }
507
508        @Override
509        public void reset() throws IOException {
510            reader.reset();
511        }
512
513        public InputStream toOriginalInputStream() throws FileNotFoundException {
514            return new FileInputStream(file);
515        }
516    }
517
518    /**
519     * Encoding-aware file reader. 
520     */
521    private static class EncodingFileReader extends InputStreamReader {
522
523        private final FileInputStream in;
524
525        /**
526         * @param in file to read
527         * @param charset character set to use
528         */
529        EncodingFileReader(FileInputStream in, String charset)
530            throws FileNotFoundException, UnsupportedEncodingException {
531            super(in, charset);
532            this.in = in;
533        }
534
535        @Override
536        public void close() throws IOException {
537            try {
538                super.close();
539            } finally {
540                in.close();
541            }
542        }
543    }
544    
545    /**
546     * Encoding-aware file writer. 
547     */
548    private static class EncodingFileWriter extends OutputStreamWriter {
549
550        private final FileOutputStream out;
551
552        /**
553         * @param out file to write
554         * @param charset character set to use
555         */
556        EncodingFileWriter(FileOutputStream out, String charset)
557            throws FileNotFoundException, UnsupportedEncodingException {
558            super(out, charset);
559            this.out = out;
560        }
561        
562        /**
563         * @param out file to write
564         * @param enc character set to use
565         */
566        EncodingFileWriter(FileOutputStream out, CharsetEncoder enc)
567            throws FileNotFoundException, UnsupportedEncodingException {
568            super(out, enc);
569            this.out = out;
570        }
571
572        @Override
573        public void close() throws IOException {
574            try {
575                super.close();
576            } finally {
577                out.close();
578            }
579        }
580    }
581    
582    /**
583     * This method will take off the quotes and double quotes of the charset
584     */
585    @Deprecated
586    public static String normalizeCharset(String charset) {
587        return IOHelper.normalizeCharset(charset);
588    }
589    
590    @Deprecated
591    public static void validateCharset(String charset) throws UnsupportedCharsetException {
592        IOHelper.validateCharset(charset);
593    }
594
595}