    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 * <p>
    075 * Instances of {@link ReaderInputStream} are not thread safe.
    076 * </p>
    077 *
    078 * @see org.apache.commons.io.output.WriterOutputStream
    079 *
    080 * @since 2.0
    081 */
    082public class ReaderInputStream extends InputStream {
    083    private static final int DEFAULT_BUFFER_SIZE = 1024;
    084
    085    private final Reader reader;
    086    private final CharsetEncoder encoder;
    087
    088    /**
    089     * CharBuffer used as input for the decoder. It should be reasonably
    090     * large as we read data from the underlying Reader into this buffer.
    091     */
    092    private final CharBuffer encoderIn;
    093
    094    /**
    095     * ByteBuffer used as output for the decoder. This buffer can be small
    096     * as it is only used to transfer data from the decoder to the
    097     * buffer provided by the caller.
    098     */
    099    private final ByteBuffer encoderOut;
    100
    101    private CoderResult lastCoderResult;
    102    private boolean endOfInput;
    103
    104    /**
    105     * Construct a new {@link ReaderInputStream}.
    106     *
    107     * @param reader the target {@link Reader}
    108     * @param encoder the charset encoder
    109     * @since 2.1
    110     */
    111    public ReaderInputStream(final Reader reader, final CharsetEncoder encoder) {
    112        this(reader, encoder, DEFAULT_BUFFER_SIZE);
    113    }
    114
    115    /**
    116     * Construct a new {@link ReaderInputStream}.
    117     *
    118     * @param reader the target {@link Reader}
    119     * @param encoder the charset encoder
    120     * @param bufferSize the size of the input buffer in number of characters
    121     * @since 2.1
    122     */
    123    public ReaderInputStream(final Reader reader, final CharsetEncoder encoder, final int bufferSize) {
    124        this.reader = reader;
    125        this.encoder = encoder;
    126        this.encoderIn = CharBuffer.allocate(bufferSize);
    127        this.encoderIn.flip();
    128        this.encoderOut = ByteBuffer.allocate(128);
    129        this.encoderOut.flip();
    130    }
    131
    132    /**
    133     * Construct a new {@link ReaderInputStream}.
    134     *
    135     * @param reader the target {@link Reader}
    136     * @param charset the charset encoding
    137     * @param bufferSize the size of the input buffer in number of characters
    138     */
    139    public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) {
    140        this(reader,
    141             charset.newEncoder()
    142                    .onMalformedInput(CodingErrorAction.REPLACE)
    143                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
    144             bufferSize);
    145    }
    146
    147    /**
    148     * Construct a new {@link ReaderInputStream} with a default input buffer size of
    149     * {@value #DEFAULT_BUFFER_SIZE} characters.
    150     *
    151     * @param reader the target {@link Reader}
    152     * @param charset the charset encoding
    153     */
    154    public ReaderInputStream(final Reader reader, final Charset charset) {
    155        this(reader, charset, DEFAULT_BUFFER_SIZE);
    156    }
    157
    158    /**
    159     * Construct a new {@link ReaderInputStream}.
    160     *
    161     * @param reader the target {@link Reader}
    162     * @param charsetName the name of the charset encoding
    163     * @param bufferSize the size of the input buffer in number of characters
    164     */
    165    public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) {
    166        this(reader, Charset.forName(charsetName), bufferSize);
    167    }
    168
    169    /**
    170     * Construct a new {@link ReaderInputStream} with a default input buffer size of
    171     * {@value #DEFAULT_BUFFER_SIZE} characters.
    172     *
    173     * @param reader the target {@link Reader}
    174     * @param charsetName the name of the charset encoding
    175     */
    176    public ReaderInputStream(final Reader reader, final String charsetName) {
    177        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
    178    }
    179
    180    /**
    181     * Construct a new {@link ReaderInputStream} that uses the default character encoding
    182     * with a default input buffer size of {@value #DEFAULT_BUFFER_SIZE} characters.
    183     *
    184     * @param reader the target {@link Reader}
    185     * @deprecated 2.5 use {@link #ReaderInputStream(Reader, Charset)} instead
    186     */
    187    @Deprecated
    188    public ReaderInputStream(final Reader reader) {
    189        this(reader, Charset.defaultCharset());
    190    }
    191
    192    /**
    193     * Fills the internal char buffer from the reader.
    194     *
    195     * @throws IOException
    196     *             If an I/O error occurs
    197     */
    198    private void fillBuffer() throws IOException {
    199        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
    200            encoderIn.compact();
    201            final int position = encoderIn.position();
    202            // We don't use Reader#read(CharBuffer) here because it is more efficient
    203            // to write directly to the underlying char array (the default implementation
    204            // copies data to a temporary char array).
    205            final int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
    206            if (c == EOF) {
    207                endOfInput = true;
    208            } else {
    209                encoderIn.position(position+c);
    210            }
    211            encoderIn.flip();
    212        }
    213        encoderOut.compact();
    214        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
    215        encoderOut.flip();
    216    }
    217
    218    /**
    219     * Read the specified number of bytes into an array.
    220     *
    221     * @param array the byte array to read into
    222     * @param off the offset to start reading bytes into
    223     * @param len the number of bytes to read
    224     * @return the number of bytes read or <code>-1</code>
    225     *         if the end of the stream has been reached
    226     * @throws IOException if an I/O error occurs
    227     */
    228    @Override
    229    public int read(final byte[] array, int off, int len) throws IOException {
    230        Objects.requireNonNull(array, "array");
    231        if (len < 0 || off < 0 || (off + len) > array.length) {
    232            throw new IndexOutOfBoundsException("Array Size=" + array.length +
    233                    ", offset=" + off + ", length=" + len);
    234        }
    235        int read = 0;
    236        if (len == 0) {
    237            return 0; // Always return 0 if len == 0
    238        }
    239        while (len > 0) {
    240            if (encoderOut.hasRemaining()) {
    241                final int c = Math.min(encoderOut.remaining(), len);
    242                encoderOut.get(array, off, c);
    243                off += c;
    244                len -= c;
    245                read += c;
    246            } else {
    247                fillBuffer();
    248                if (endOfInput && !encoderOut.hasRemaining()) {
    249                    break;
    250                }
    251            }
    252        }
    253        return read == 0 && endOfInput ? EOF : read;
    254    }
    255
    256    /**
    257     * Read the specified number of bytes into an array.
    258     *
    259     * @param b the byte array to read into
    260     * @return the number of bytes read or <code>-1</code>
    261     *         if the end of the stream has been reached
    262     * @throws IOException if an I/O error occurs
    263     */
    264    @Override
    265    public int read(final byte[] b) throws IOException {
    266        return read(b, 0, b.length);
    267    }
    268
    269    /**
    270     * Read a single byte.
    271     *
    272     * @return either the byte read or <code>-1</code> if the end of the stream
    273     *         has been reached
    274     * @throws IOException if an I/O error occurs
    275     */
    276    @Override
    277    public int read() throws IOException {
    278        for (;;) {
    279            if (encoderOut.hasRemaining()) {
    280                return encoderOut.get() & 0xFF;
    281            }
    282            fillBuffer();
    283            if (endOfInput && !encoderOut.hasRemaining()) {
    284                return EOF;
    285            }
    286        }
    287    }
    288
    289    /**
    290     * Close the stream. This method will cause the underlying {@link Reader}
    291     * to be closed.
    292     * @throws IOException if an I/O error occurs
    293     */
    294    @Override
    295    public void close() throws IOException {
    296        reader.close();
    297    }
    298}