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