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
18package java.io;
19
20import java.nio.ByteBuffer;
21import java.nio.CharBuffer;
22import java.nio.charset.Charset;
23import java.nio.charset.CharsetDecoder;
24import java.nio.charset.CoderResult;
25import java.nio.charset.CodingErrorAction;
26import java.nio.charset.MalformedInputException;
27import java.nio.charset.UnmappableCharacterException;
28import 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 */
40public 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