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