    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.commons.io.input;
    018
    019import static org.apache.commons.io.IOUtils.EOF;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.Reader;
    024import java.nio.ByteBuffer;
    025import java.nio.CharBuffer;
    026import java.nio.charset.Charset;
    027import java.nio.charset.CharsetEncoder;
    028import java.nio.charset.CoderResult;
    029import java.nio.charset.CodingErrorAction;
    030import java.util.Objects;
    031
    032/**
    033 * {@link InputStream} implementation that reads a character stream from a {@link Reader}
    034 * and transforms it to a byte stream using a specified charset encoding. The stream
    035 * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
    036 * encodings supported by the JRE are handled correctly. In particular for charsets such as
    037 * UTF-16, the implementation ensures that one and only one byte order marker
    038 * is produced.
    039 * <p>
    040 * Since in general it is not possible to predict the number of characters to be read from the
    041 * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
    042 * the {@link Reader} are buffered. There is therefore no well defined correlation
    043 * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
    044 * This also implies that in general there is no need to wrap the underlying {@link Reader}
    045 * in a {@link java.io.BufferedReader}.
    046 * <p>
    047 * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
    048 * in the following example, reading from {@code in2} would return the same byte
    049 * sequence as reading from {@code in} (provided that the initial byte sequence is legal
    050 * with respect to the charset encoding):
    051 * <pre>
    052 * InputStream in = ...
    053 * Charset cs = ...
    054 * InputStreamReader reader = new InputStreamReader(in, cs);
    055 * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
    056 * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
    057 * except that the control flow is reversed: both classes transform a character stream
    058 * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
    059 * while {@link ReaderInputStream} pulls it from the underlying stream.
    060 * <p>
    061 * Note that while there are use cases where there is no alternative to using
    062 * this class, very often the need to use this class is an indication of a flaw
    063 * in the design of the code. This class is typically used in situations where an existing
    064 * API only accepts an {@link InputStream}, but where the most natural way to produce the data
    065 * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
    066 * where this problem may appear is when implementing the {@code javax.activation.DataSource}
    067 * interface from the Java Activation Framework.
    068 * <p>
    069 * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
    070 * read operation will block or not, it is not possible to provide a meaningful
    071 * implementation of the {@link InputStream#available()} method. A call to this method
    072 * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
    073 * <p>
    074 * Instances of {@link ReaderInputStream} are not thread safe.
    075 *
    076 * @see org.apache.commons.io.output.WriterOutputStream
    077 *
    078 * @since 2.0
    079 */
    080public class ReaderInputStream extends InputStream {
    081    private static final int DEFAULT_BUFFER_SIZE = 1024;
    082
    083    private final Reader reader;
    084    private final CharsetEncoder encoder;
    085
    086    /**
    087     * CharBuffer used as input for the decoder. It should be reasonably
    088     * large as we read data from the underlying Reader into this buffer.
    089     */
    090    private final CharBuffer encoderIn;
    091
    092    /**
    093     * ByteBuffer used as output for the decoder. This buffer can be small
    094     * as it is only used to transfer data from the decoder to the
    095     * buffer provided by the caller.
    096     */
    097    private final ByteBuffer encoderOut;
    098
    099    private CoderResult lastCoderResult;
    100    private boolean endOfInput;
    101
    102    /**
    103     * Construct a new {@link ReaderInputStream}.
    104     *
    105     * @param reader the target {@link Reader}
    106     * @param encoder the charset encoder
    107     * @since 2.1
    108     */
    109    public ReaderInputStream(final Reader reader, final CharsetEncoder encoder) {
    110        this(reader, encoder, DEFAULT_BUFFER_SIZE);
    111    }
    112
    113    /**
    114     * Construct a new {@link ReaderInputStream}.
    115     *
    116     * @param reader the target {@link Reader}
    117     * @param encoder the charset encoder
    118     * @param bufferSize the size of the input buffer in number of characters
    119     * @since 2.1
    120     */
    121    public ReaderInputStream(final Reader reader, final CharsetEncoder encoder, final int bufferSize) {
    122        this.reader = reader;
    123        this.encoder = encoder;
    124        this.encoderIn = CharBuffer.allocate(bufferSize);
    125        this.encoderIn.flip();
    126        this.encoderOut = ByteBuffer.allocate(128);
    127        this.encoderOut.flip();
    128    }
    129
    130    /**
    131     * Construct a new {@link ReaderInputStream}.
    132     *
    133     * @param reader the target {@link Reader}
    134     * @param charset the charset encoding
    135     * @param bufferSize the size of the input buffer in number of characters
    136     */
    137    public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) {
    138        this(reader,
    139             charset.newEncoder()
    140                    .onMalformedInput(CodingErrorAction.REPLACE)
    141                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
    142             bufferSize);
    143    }
    144
    145    /**
    146     * Construct a new {@link ReaderInputStream} with a default input buffer size of
    147     * {@value #DEFAULT_BUFFER_SIZE} characters.
    148     *
    149     * @param reader the target {@link Reader}
    150     * @param charset the charset encoding
    151     */
    152    public ReaderInputStream(final Reader reader, final Charset charset) {
    153        this(reader, charset, DEFAULT_BUFFER_SIZE);
    154    }
    155
    156    /**
    157     * Construct a new {@link ReaderInputStream}.
    158     *
    159     * @param reader the target {@link Reader}
    160     * @param charsetName the name of the charset encoding
    161     * @param bufferSize the size of the input buffer in number of characters
    162     */
    163    public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) {
    164        this(reader, Charset.forName(charsetName), bufferSize);
    165    }
    166
    167    /**
    168     * Construct a new {@link ReaderInputStream} with a default input buffer size of
    169     * {@value #DEFAULT_BUFFER_SIZE} characters.
    170     *
    171     * @param reader the target {@link Reader}
    172     * @param charsetName the name of the charset encoding
    173     */
    174    public ReaderInputStream(final Reader reader, final String charsetName) {
    175        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
    176    }
    177
    178    /**
    179     * Construct a new {@link ReaderInputStream} that uses the default character encoding
    180     * with a default input buffer size of {@value #DEFAULT_BUFFER_SIZE} characters.
    181     *
    182     * @param reader the target {@link Reader}
    183     * @deprecated 2.5 use {@link #ReaderInputStream(Reader, Charset)} instead
    184     */
    185    @Deprecated
    186    public ReaderInputStream(final Reader reader) {
    187        this(reader, Charset.defaultCharset());
    188    }
    189
    190    /**
    191     * Fills the internal char buffer from the reader.
    192     *
    193     * @throws IOException
    194     *             If an I/O error occurs
    195     */
    196    private void fillBuffer() throws IOException {
    197        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
    198            encoderIn.compact();
    199            final int position = encoderIn.position();
    200            // We don't use Reader#read(CharBuffer) here because it is more efficient
    201            // to write directly to the underlying char array (the default implementation
    202            // copies data to a temporary char array).
    203            final int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
    204            if (c == EOF) {
    205                endOfInput = true;
    206            } else {
    207                encoderIn.position(position+c);
    208            }
    209            encoderIn.flip();
    210        }
    211        encoderOut.compact();
    212        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
    213        encoderOut.flip();
    214    }
    215
    216    /**
    217     * Read the specified number of bytes into an array.
    218     *
    219     * @param array the byte array to read into
    220     * @param off the offset to start reading bytes into
    221     * @param len the number of bytes to read
    222     * @return the number of bytes read or <code>-1</code>
    223     *         if the end of the stream has been reached
    224     * @throws IOException if an I/O error occurs
    225     */
    226    @Override
    227    public int read(final byte[] array, int off, int len) throws IOException {
    228        Objects.requireNonNull(array, "array");
    229        if (len < 0 || off < 0 || (off + len) > array.length) {
    230            throw new IndexOutOfBoundsException("Array Size=" + array.length +
    231                    ", offset=" + off + ", length=" + len);
    232        }
    233        int read = 0;
    234        if (len == 0) {
    235            return 0; // Always return 0 if len == 0
    236        }
    237        while (len > 0) {
    238            if (encoderOut.hasRemaining()) {
    239                final int c = Math.min(encoderOut.remaining(), len);
    240                encoderOut.get(array, off, c);
    241                off += c;
    242                len -= c;
    243                read += c;
    244            } else {
    245                fillBuffer();
    246                if (endOfInput && !encoderOut.hasRemaining()) {
    247                    break;
    248                }
    249            }
    250        }
    251        return read == 0 && endOfInput ? EOF : read;
    252    }
    253
    254    /**
    255     * Read the specified number of bytes into an array.
    256     *
    257     * @param b the byte array to read into
    258     * @return the number of bytes read or <code>-1</code>
    259     *         if the end of the stream has been reached
    260     * @throws IOException if an I/O error occurs
    261     */
    262    @Override
    263    public int read(final byte[] b) throws IOException {
    264        return read(b, 0, b.length);
    265    }
    266
    267    /**
    268     * Read a single byte.
    269     *
    270     * @return either the byte read or <code>-1</code> if the end of the stream
    271     *         has been reached
    272     * @throws IOException if an I/O error occurs
    273     */
    274    @Override
    275    public int read() throws IOException {
    276        for (;;) {
    277            if (encoderOut.hasRemaining()) {
    278                return encoderOut.get() & 0xFF;
    279            }
    280            fillBuffer();
    281            if (endOfInput && !encoderOut.hasRemaining()) {
    282                return EOF;
    283            }
    284        }
    285    }
    286
    287    /**
    288     * Close the stream. This method will cause the underlying {@link Reader}
    289     * to be closed.
    290     * @throws IOException if an I/O error occurs
    291     */
    292    @Override
    293    public void close() throws IOException {
    294        reader.close();
    295    }
    296}