Coverage Report - org.apache.commons.io.input.ReaderInputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
ReaderInputStream
91%
54/59
80%
29/36
2.917
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.io.input;
 18  
 
 19  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.io.Reader;
 22  
 import java.nio.ByteBuffer;
 23  
 import java.nio.CharBuffer;
 24  
 import java.nio.charset.Charset;
 25  
 import java.nio.charset.CharsetEncoder;
 26  
 import java.nio.charset.CoderResult;
 27  
 import java.nio.charset.CodingErrorAction;
 28  
 
 29  
 /**
 30  
  * {@link InputStream} implementation that reads a character stream from a {@link Reader}
 31  
  * and transforms it to a byte stream using a specified charset encoding. The stream
 32  
  * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
 33  
  * encodings supported by the JRE are handled correctly. In particular for charsets such as
 34  
  * UTF-16, the implementation ensures that one and only one byte order marker
 35  
  * is produced.
 36  
  * <p>
 37  
  * Since in general it is not possible to predict the number of characters to be read from the
 38  
  * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
 39  
  * the {@link Reader} are buffered. There is therefore no well defined correlation
 40  
  * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
 41  
  * This also implies that in general there is no need to wrap the underlying {@link Reader}
 42  
  * in a {@link java.io.BufferedReader}.
 43  
  * <p>
 44  
  * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
 45  
  * in the following example, reading from <tt>in2</tt> would return the same byte
 46  
  * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
 47  
  * with respect to the charset encoding):
 48  
  * <pre>
 49  
  * InputStream in = ...
 50  
  * Charset cs = ...
 51  
  * InputStreamReader reader = new InputStreamReader(in, cs);
 52  
  * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
 53  
  * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
 54  
  * except that the control flow is reversed: both classes transform a character stream
 55  
  * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
 56  
  * while {@link ReaderInputStream} pulls it from the underlying stream.
 57  
  * <p>
 58  
  * Note that while there are use cases where there is no alternative to using
 59  
  * this class, very often the need to use this class is an indication of a flaw
 60  
  * in the design of the code. This class is typically used in situations where an existing
 61  
  * API only accepts an {@link InputStream}, but where the most natural way to produce the data
 62  
  * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
 63  
  * where this problem may appear is when implementing the {@link javax.activation.DataSource}
 64  
  * interface from the Java Activation Framework.
 65  
  * <p>
 66  
  * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
 67  
  * read operation will block or not, it is not possible to provide a meaningful
 68  
  * implementation of the {@link InputStream#available()} method. A call to this method
 69  
  * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
 70  
  * <p>
 71  
  * Instances of {@link ReaderInputStream} are not thread safe.
 72  
  *
 73  
  * @see org.apache.commons.io.output.WriterOutputStream
 74  
  *
 75  
  * @since 2.0
 76  
  */
 77  
 public class ReaderInputStream extends InputStream {
 78  
     private static final int DEFAULT_BUFFER_SIZE = 1024;
 79  
 
 80  
     private final Reader reader;
 81  
     private final CharsetEncoder encoder;
 82  
 
 83  
     /**
 84  
      * CharBuffer used as input for the decoder. It should be reasonably
 85  
      * large as we read data from the underlying Reader into this buffer.
 86  
      */
 87  
     private final CharBuffer encoderIn;
 88  
 
 89  
     /**
 90  
      * ByteBuffer used as output for the decoder. This buffer can be small
 91  
      * as it is only used to transfer data from the decoder to the
 92  
      * buffer provided by the caller.
 93  
      */
 94  
     private final ByteBuffer encoderOut;
 95  
 
 96  
     private CoderResult lastCoderResult;
 97  
     private boolean endOfInput;
 98  
 
 99  
     /**
 100  
      * Construct a new {@link ReaderInputStream}.
 101  
      *
 102  
      * @param reader the target {@link Reader}
 103  
      * @param encoder the charset encoder
 104  
      * @since 2.1
 105  
      */
 106  
     public ReaderInputStream(final Reader reader, final CharsetEncoder encoder) {
 107  0
         this(reader, encoder, DEFAULT_BUFFER_SIZE);
 108  0
     }
 109  
 
 110  
     /**
 111  
      * Construct a new {@link ReaderInputStream}.
 112  
      *
 113  
      * @param reader the target {@link Reader}
 114  
      * @param encoder the charset encoder
 115  
      * @param bufferSize the size of the input buffer in number of characters
 116  
      * @since 2.1
 117  
      */
 118  16
     public ReaderInputStream(final Reader reader, final CharsetEncoder encoder, final int bufferSize) {
 119  16
         this.reader = reader;
 120  16
         this.encoder = encoder;
 121  16
         this.encoderIn = CharBuffer.allocate(bufferSize);
 122  16
         this.encoderIn.flip();
 123  16
         this.encoderOut = ByteBuffer.allocate(128);
 124  16
         this.encoderOut.flip();
 125  16
     }
 126  
 
 127  
     /**
 128  
      * Construct a new {@link ReaderInputStream}.
 129  
      *
 130  
      * @param reader the target {@link Reader}
 131  
      * @param charset the charset encoding
 132  
      * @param bufferSize the size of the input buffer in number of characters
 133  
      */
 134  
     public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) {
 135  16
         this(reader,
 136  
              charset.newEncoder()
 137  
                     .onMalformedInput(CodingErrorAction.REPLACE)
 138  
                     .onUnmappableCharacter(CodingErrorAction.REPLACE),
 139  
              bufferSize);
 140  16
     }
 141  
 
 142  
     /**
 143  
      * Construct a new {@link ReaderInputStream} with a default input buffer size of
 144  
      * 1024 characters.
 145  
      *
 146  
      * @param reader the target {@link Reader}
 147  
      * @param charset the charset encoding
 148  
      */
 149  
     public ReaderInputStream(final Reader reader, final Charset charset) {
 150  6
         this(reader, charset, DEFAULT_BUFFER_SIZE);
 151  6
     }
 152  
 
 153  
     /**
 154  
      * Construct a new {@link ReaderInputStream}.
 155  
      *
 156  
      * @param reader the target {@link Reader}
 157  
      * @param charsetName the name of the charset encoding
 158  
      * @param bufferSize the size of the input buffer in number of characters
 159  
      */
 160  
     public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) {
 161  10
         this(reader, Charset.forName(charsetName), bufferSize);
 162  10
     }
 163  
 
 164  
     /**
 165  
      * Construct a new {@link ReaderInputStream} with a default input buffer size of
 166  
      * 1024 characters.
 167  
      *
 168  
      * @param reader the target {@link Reader}
 169  
      * @param charsetName the name of the charset encoding
 170  
      */
 171  
     public ReaderInputStream(final Reader reader, final String charsetName) {
 172  10
         this(reader, charsetName, DEFAULT_BUFFER_SIZE);
 173  10
     }
 174  
 
 175  
     /**
 176  
      * Construct a new {@link ReaderInputStream} that uses the default character encoding
 177  
      * with a default input buffer size of 1024 characters.
 178  
      *
 179  
      * @param reader the target {@link Reader}
 180  
      * @deprecated 2.5 use {@link #ReaderInputStream(Reader, Charset)} instead
 181  
      */
 182  
     @Deprecated
 183  
     public ReaderInputStream(final Reader reader) {
 184  4
         this(reader, Charset.defaultCharset());
 185  4
     }
 186  
 
 187  
     /**
 188  
      * Fills the internal char buffer from the reader.
 189  
      *
 190  
      * @throws IOException
 191  
      *             If an I/O error occurs
 192  
      */
 193  
     private void fillBuffer() throws IOException {
 194  192
         if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
 195  46
             encoderIn.compact();
 196  46
             final int position = encoderIn.position();
 197  
             // We don't use Reader#read(CharBuffer) here because it is more efficient
 198  
             // to write directly to the underlying char array (the default implementation
 199  
             // copies data to a temporary char array).
 200  46
             final int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
 201  46
             if (c == -1) {
 202  16
                 endOfInput = true;
 203  
             } else {
 204  30
                 encoderIn.position(position+c);
 205  
             }
 206  46
             encoderIn.flip();
 207  
         }
 208  192
         encoderOut.compact();
 209  192
         lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
 210  192
         encoderOut.flip();
 211  192
     }
 212  
 
 213  
     /**
 214  
      * Read the specified number of bytes into an array.
 215  
      *
 216  
      * @param b the byte array to read into
 217  
      * @param off the offset to start reading bytes into
 218  
      * @param len the number of bytes to read
 219  
      * @return the number of bytes read or <code>-1</code>
 220  
      *         if the end of the stream has been reached
 221  
      * @throws IOException if an I/O error occurs
 222  
      */
 223  
     @Override
 224  
     public int read(final byte[] b, int off, int len) throws IOException {
 225  328
         if (b == null) {
 226  0
             throw new NullPointerException("Byte array must not be null");
 227  
         }
 228  328
         if (len < 0 || off < 0 || (off + len) > b.length) {
 229  0
             throw new IndexOutOfBoundsException("Array Size=" + b.length +
 230  
                     ", offset=" + off + ", length=" + len);
 231  
         }
 232  328
         int read = 0;
 233  328
         if (len == 0) {
 234  10
             return 0; // Always return 0 if len == 0
 235  
         }
 236  785
         while (len > 0) {
 237  481
             if (encoderOut.hasRemaining()) {
 238  383
                 final int c = Math.min(encoderOut.remaining(), len);
 239  383
                 encoderOut.get(b, off, c);
 240  383
                 off += c;
 241  383
                 len -= c;
 242  383
                 read += c;
 243  383
             } else {
 244  98
                 fillBuffer();
 245  98
                 if (endOfInput && !encoderOut.hasRemaining()) {
 246  14
                     break;
 247  
                 }
 248  
             }
 249  
         }
 250  318
         return read == 0 && endOfInput ? -1 : read;
 251  
     }
 252  
 
 253  
     /**
 254  
      * Read the specified number of bytes into an array.
 255  
      *
 256  
      * @param b the byte array to read into
 257  
      * @return the number of bytes read or <code>-1</code>
 258  
      *         if the end of the stream has been reached
 259  
      * @throws IOException if an I/O error occurs
 260  
      */
 261  
     @Override
 262  
     public int read(final byte[] b) throws IOException {
 263  0
         return read(b, 0, b.length);
 264  
     }
 265  
 
 266  
     /**
 267  
      * Read a single byte.
 268  
      *
 269  
      * @return either the byte read or <code>-1</code> if the end of the stream
 270  
      *         has been reached
 271  
      * @throws IOException if an I/O error occurs
 272  
      */
 273  
     @Override
 274  
     public int read() throws IOException {
 275  
         for (;;) {
 276  9980
             if (encoderOut.hasRemaining()) {
 277  9886
                 return encoderOut.get() & 0xFF;
 278  
             } else {
 279  94
                 fillBuffer();
 280  94
                 if (endOfInput && !encoderOut.hasRemaining()) {
 281  8
                     return -1;
 282  
                 }
 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  16
         reader.close();
 295  16
     }
 296  
 }