Home | History | Annotate | Download | only in io
      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 
     18 package java.io;
     19 
     20 import java.nio.ByteBuffer;
     21 import java.nio.CharBuffer;
     22 import java.nio.charset.Charset;
     23 import java.nio.charset.CharsetDecoder;
     24 import java.nio.charset.CoderResult;
     25 import java.nio.charset.CodingErrorAction;
     26 import java.nio.charset.MalformedInputException;
     27 import java.nio.charset.UnmappableCharacterException;
     28 import java.util.Arrays;
     29 
     30 /**
     31  * A class for turning a byte stream into a character stream. Data read from the
     32  * source input stream is converted into characters by either a default or a
     33  * provided character converter. The default encoding is taken from the
     34  * "file.encoding" system property. {@code InputStreamReader} contains a buffer
     35  * of bytes read from the source stream and converts these into characters as
     36  * needed. The buffer size is 8K.
     37  *
     38  * @see OutputStreamWriter
     39  */
     40 public class InputStreamReader extends Reader {
     41     private InputStream in;
     42 
     43     private boolean endOfInput = false;
     44 
     45     private CharsetDecoder decoder;
     46 
     47     private final ByteBuffer bytes = ByteBuffer.allocate(8192);
     48 
     49     /**
     50      * Constructs a new {@code InputStreamReader} on the {@link InputStream}
     51      * {@code in}. This constructor sets the character converter to the encoding
     52      * specified in the "file.encoding" property and falls back to ISO 8859_1
     53      * (ISO-Latin-1) if the property doesn't exist.
     54      *
     55      * @param in
     56      *            the input stream from which to read characters.
     57      */
     58     public InputStreamReader(InputStream in) {
     59         this(in, Charset.defaultCharset());
     60     }
     61 
     62     /**
     63      * Constructs a new InputStreamReader on the InputStream {@code in}. The
     64      * character converter that is used to decode bytes into characters is
     65      * identified by name by {@code charsetName}. If the encoding cannot be found, an
     66      * UnsupportedEncodingException error is thrown.
     67      *
     68      * @param in
     69      *            the InputStream from which to read characters.
     70      * @param charsetName
     71      *            identifies the character converter to use.
     72      * @throws NullPointerException
     73      *             if {@code charsetName} is {@code null}.
     74      * @throws UnsupportedEncodingException
     75      *             if the encoding specified by {@code charsetName} cannot be found.
     76      */
     77     public InputStreamReader(InputStream in, final String charsetName)
     78             throws UnsupportedEncodingException {
     79         super(in);
     80         if (charsetName == null) {
     81             throw new NullPointerException("charsetName == null");
     82         }
     83         this.in = in;
     84         try {
     85             decoder = Charset.forName(charsetName).newDecoder().onMalformedInput(
     86                     CodingErrorAction.REPLACE).onUnmappableCharacter(
     87                     CodingErrorAction.REPLACE);
     88         } catch (IllegalArgumentException e) {
     89             throw (UnsupportedEncodingException)
     90                     new UnsupportedEncodingException(charsetName).initCause(e);
     91         }
     92         bytes.limit(0);
     93     }
     94 
     95     /**
     96      * Constructs a new InputStreamReader on the InputStream {@code in} and
     97      * CharsetDecoder {@code dec}.
     98      *
     99      * @param in
    100      *            the source InputStream from which to read characters.
    101      * @param dec
    102      *            the CharsetDecoder used by the character conversion.
    103      */
    104     public InputStreamReader(InputStream in, CharsetDecoder dec) {
    105         super(in);
    106         dec.averageCharsPerByte();
    107         this.in = in;
    108         decoder = dec;
    109         bytes.limit(0);
    110     }
    111 
    112     /**
    113      * Constructs a new InputStreamReader on the InputStream {@code in} and
    114      * Charset {@code charset}.
    115      *
    116      * @param in
    117      *            the source InputStream from which to read characters.
    118      * @param charset
    119      *            the Charset that defines the character converter
    120      */
    121     public InputStreamReader(InputStream in, Charset charset) {
    122         super(in);
    123         this.in = in;
    124         decoder = charset.newDecoder().onMalformedInput(
    125                 CodingErrorAction.REPLACE).onUnmappableCharacter(
    126                 CodingErrorAction.REPLACE);
    127         bytes.limit(0);
    128     }
    129 
    130     /**
    131      * Closes this reader. This implementation closes the source InputStream and
    132      * releases all local storage.
    133      *
    134      * @throws IOException
    135      *             if an error occurs attempting to close this reader.
    136      */
    137     @Override
    138     public void close() throws IOException {
    139         synchronized (lock) {
    140             if (decoder != null) {
    141                 decoder.reset();
    142             }
    143             decoder = null;
    144             if (in != null) {
    145                 in.close();
    146                 in = null;
    147             }
    148         }
    149     }
    150 
    151     /**
    152      * Returns the canonical name of the encoding used by this writer to convert characters to
    153      * bytes, or null if this writer has been closed. Most callers should probably keep
    154      * track of the String or Charset they passed in; this method may not return the same
    155      * name.
    156      */
    157     public String getEncoding() {
    158         if (!isOpen()) {
    159             return null;
    160         }
    161         return decoder.charset().name();
    162     }
    163 
    164     /**
    165      * Reads a single character from this reader and returns it as an integer
    166      * with the two higher-order bytes set to 0. Returns -1 if the end of the
    167      * reader has been reached. The byte value is either obtained from
    168      * converting bytes in this reader's buffer or by first filling the buffer
    169      * from the source InputStream and then reading from the buffer.
    170      *
    171      * @return the character read or -1 if the end of the reader has been
    172      *         reached.
    173      * @throws IOException
    174      *             if this reader is closed or some other I/O error occurs.
    175      */
    176     @Override
    177     public int read() throws IOException {
    178         synchronized (lock) {
    179             if (!isOpen()) {
    180                 throw new IOException("InputStreamReader is closed");
    181             }
    182             char[] buf = new char[1];
    183             return read(buf, 0, 1) != -1 ? buf[0] : -1;
    184         }
    185     }
    186 
    187     /**
    188      * Reads up to {@code count} characters from this reader and stores them
    189      * at position {@code offset} in the character array {@code buffer}. Returns
    190      * the number of characters actually read or -1 if the end of the reader has
    191      * been reached. The bytes are either obtained from converting bytes in this
    192      * reader's buffer or by first filling the buffer from the source
    193      * InputStream and then reading from the buffer.
    194      *
    195      * @throws IndexOutOfBoundsException
    196      *     if {@code offset < 0 || count < 0 || offset + count > buffer.length}.
    197      * @throws IOException
    198      *             if this reader is closed or some other I/O error occurs.
    199      */
    200     @Override
    201     public int read(char[] buffer, int offset, int count) throws IOException {
    202         synchronized (lock) {
    203             if (!isOpen()) {
    204                 throw new IOException("InputStreamReader is closed");
    205             }
    206 
    207             Arrays.checkOffsetAndCount(buffer.length, offset, count);
    208             if (count == 0) {
    209                 return 0;
    210             }
    211 
    212             CharBuffer out = CharBuffer.wrap(buffer, offset, count);
    213             CoderResult result = CoderResult.UNDERFLOW;
    214 
    215             // bytes.remaining() indicates number of bytes in buffer
    216             // when 1-st time entered, it'll be equal to zero
    217             boolean needInput = !bytes.hasRemaining();
    218 
    219             while (out.hasRemaining()) {
    220                 // fill the buffer if needed
    221                 if (needInput) {
    222                     try {
    223                         if (in.available() == 0 && out.position() > offset) {
    224                             // we could return the result without blocking read
    225                             break;
    226                         }
    227                     } catch (IOException e) {
    228                         // available didn't work so just try the read
    229                     }
    230 
    231                     int desiredByteCount = bytes.capacity() - bytes.limit();
    232                     int off = bytes.arrayOffset() + bytes.limit();
    233                     int actualByteCount = in.read(bytes.array(), off, desiredByteCount);
    234 
    235                     if (actualByteCount == -1) {
    236                         endOfInput = true;
    237                         break;
    238                     } else if (actualByteCount == 0) {
    239                         break;
    240                     }
    241                     bytes.limit(bytes.limit() + actualByteCount);
    242                     needInput = false;
    243                 }
    244 
    245                 // decode bytes
    246                 result = decoder.decode(bytes, out, false);
    247 
    248                 if (result.isUnderflow()) {
    249                     // compact the buffer if no space left
    250                     if (bytes.limit() == bytes.capacity()) {
    251                         bytes.compact();
    252                         bytes.limit(bytes.position());
    253                         bytes.position(0);
    254                     }
    255                     needInput = true;
    256                 } else {
    257                     break;
    258                 }
    259             }
    260 
    261             if (result == CoderResult.UNDERFLOW && endOfInput) {
    262                 result = decoder.decode(bytes, out, true);
    263                 decoder.flush(out);
    264                 decoder.reset();
    265             }
    266             if (result.isMalformed() || result.isUnmappable()) {
    267                 result.throwException();
    268             }
    269 
    270             return out.position() - offset == 0 ? -1 : out.position() - offset;
    271         }
    272     }
    273 
    274     private boolean isOpen() {
    275         return in != null;
    276     }
    277 
    278     /**
    279      * Indicates whether this reader is ready to be read without blocking. If
    280      * the result is {@code true}, the next {@code read()} will not block. If
    281      * the result is {@code false} then this reader may or may not block when
    282      * {@code read()} is called. This implementation returns {@code true} if
    283      * there are bytes available in the buffer or the source stream has bytes
    284      * available.
    285      *
    286      * @return {@code true} if the receiver will not block when {@code read()}
    287      *         is called, {@code false} if unknown or blocking will occur.
    288      * @throws IOException
    289      *             if this reader is closed or some other I/O error occurs.
    290      */
    291     @Override
    292     public boolean ready() throws IOException {
    293         synchronized (lock) {
    294             if (in == null) {
    295                 throw new IOException("InputStreamReader is closed");
    296             }
    297             try {
    298                 return bytes.hasRemaining() || in.available() > 0;
    299             } catch (IOException e) {
    300                 return false;
    301             }
    302         }
    303     }
    304 }
    305