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