View Javadoc

1   /*
2    * Copyright (c) 2002-2012, the original author or authors.
3    *
4    * This software is distributable under the BSD license. See the terms of the
5    * BSD license in the documentation provided with this software.
6    *
7    * http://www.opensource.org/licenses/bsd-license.php
8    */
9   package jline.internal;
10  
11  import java.io.IOException;
12  import java.io.InputStream;
13  import java.io.OutputStreamWriter;
14  import java.io.Reader;
15  import java.io.UnsupportedEncodingException;
16  import java.nio.ByteBuffer;
17  import java.nio.CharBuffer;
18  import java.nio.charset.Charset;
19  import java.nio.charset.CharsetDecoder;
20  import java.nio.charset.CoderResult;
21  import java.nio.charset.CodingErrorAction;
22  import java.nio.charset.MalformedInputException;
23  import java.nio.charset.UnmappableCharacterException;
24  
25  
26  /**
27   *
28   * NOTE for JLine: the default InputStreamReader that comes from the JRE
29   * usually read more bytes than needed from the input stream, which
30   * is not usable in a character per character model used in the console.
31   * We thus use the harmony code which only reads the minimal number of bytes,
32   * with a modification to ensure we can read larger characters (UTF-16 has
33   * up to 4 bytes, and UTF-32, rare as it is, may have up to 8).
34   */
35  /**
36   * A class for turning a byte stream into a character stream. Data read from the
37   * source input stream is converted into characters by either a default or a
38   * provided character converter. The default encoding is taken from the
39   * "file.encoding" system property. {@code InputStreamReader} contains a buffer
40   * of bytes read from the source stream and converts these into characters as
41   * needed. The buffer size is 8K.
42   * 
43   * @see OutputStreamWriter
44   */
45  public class InputStreamReader extends Reader {
46      private InputStream in;
47  
48      private static final int BUFFER_SIZE = 8192;
49  
50      private boolean endOfInput = false;
51  
52      String encoding;
53  
54      CharsetDecoder decoder;
55  
56      ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE);
57  
58      /**
59       * Constructs a new {@code InputStreamReader} on the {@link InputStream}
60       * {@code in}. This constructor sets the character converter to the encoding
61       * specified in the "file.encoding" property and falls back to ISO 8859_1
62       * (ISO-Latin-1) if the property doesn't exist.
63       * 
64       * @param in
65       *            the input stream from which to read characters.
66       */
67      public InputStreamReader(InputStream in) {
68          super(in);
69          this.in = in;
70          // FIXME: This should probably use Configuration.getFileEncoding()
71          encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
72          decoder = Charset.forName(encoding).newDecoder().onMalformedInput(
73                  CodingErrorAction.REPLACE).onUnmappableCharacter(
74                  CodingErrorAction.REPLACE);
75          bytes.limit(0);
76      }
77  
78      /**
79       * Constructs a new InputStreamReader on the InputStream {@code in}. The
80       * character converter that is used to decode bytes into characters is
81       * identified by name by {@code enc}. If the encoding cannot be found, an
82       * UnsupportedEncodingException error is thrown.
83       * 
84       * @param in
85       *            the InputStream from which to read characters.
86       * @param enc
87       *            identifies the character converter to use.
88       * @throws NullPointerException
89       *             if {@code enc} is {@code null}.
90       * @throws UnsupportedEncodingException
91       *             if the encoding specified by {@code enc} cannot be found.
92       */
93      public InputStreamReader(InputStream in, final String enc)
94              throws UnsupportedEncodingException {
95          super(in);
96          if (enc == null) {
97              throw new NullPointerException();
98          }
99          this.in = in;
100         try {
101             decoder = Charset.forName(enc).newDecoder().onMalformedInput(
102                     CodingErrorAction.REPLACE).onUnmappableCharacter(
103                     CodingErrorAction.REPLACE);
104         } catch (IllegalArgumentException e) {
105             throw (UnsupportedEncodingException)
106                     new UnsupportedEncodingException(enc).initCause(e);
107         }
108         bytes.limit(0);
109     }
110 
111     /**
112      * Constructs a new InputStreamReader on the InputStream {@code in} and
113      * CharsetDecoder {@code dec}.
114      * 
115      * @param in
116      *            the source InputStream from which to read characters.
117      * @param dec
118      *            the CharsetDecoder used by the character conversion.
119      */
120     public InputStreamReader(InputStream in, CharsetDecoder dec) {
121         super(in);
122         dec.averageCharsPerByte();
123         this.in = in;
124         decoder = dec;
125         bytes.limit(0);
126     }
127 
128     /**
129      * Constructs a new InputStreamReader on the InputStream {@code in} and
130      * Charset {@code charset}.
131      * 
132      * @param in
133      *            the source InputStream from which to read characters.
134      * @param charset
135      *            the Charset that defines the character converter
136      */
137     public InputStreamReader(InputStream in, Charset charset) {
138         super(in);
139         this.in = in;
140         decoder = charset.newDecoder().onMalformedInput(
141                 CodingErrorAction.REPLACE).onUnmappableCharacter(
142                 CodingErrorAction.REPLACE);
143         bytes.limit(0);
144     }
145 
146     /**
147      * Closes this reader. This implementation closes the source InputStream and
148      * releases all local storage.
149      * 
150      * @throws IOException
151      *             if an error occurs attempting to close this reader.
152      */
153     @Override
154     public void close() throws IOException {
155         synchronized (lock) {
156             decoder = null;
157             if (in != null) {
158                 in.close();
159                 in = null;
160             }
161         }
162     }
163 
164     /**
165      * Returns the name of the encoding used to convert bytes into characters.
166      * The value {@code null} is returned if this reader has been closed.
167      * 
168      * @return the name of the character converter or {@code null} if this
169      *         reader is closed.
170      */
171     public String getEncoding() {
172         if (!isOpen()) {
173             return null;
174         }
175         return encoding;
176     }
177 
178     /**
179      * Reads a single character from this reader and returns it as an integer
180      * with the two higher-order bytes set to 0. Returns -1 if the end of the
181      * reader has been reached. The byte value is either obtained from
182      * converting bytes in this reader's buffer or by first filling the buffer
183      * from the source InputStream and then reading from the buffer.
184      * 
185      * @return the character read or -1 if the end of the reader has been
186      *         reached.
187      * @throws IOException
188      *             if this reader is closed or some other I/O error occurs.
189      */
190     @Override
191     public int read() throws IOException {
192         synchronized (lock) {
193             if (!isOpen()) {
194                 throw new IOException("InputStreamReader is closed.");
195             }
196 
197             char buf[] = new char[4];
198             return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1;
199         }
200     }
201 
202     /**
203      * Reads at most {@code length} characters from this reader and stores them
204      * at position {@code offset} in the character array {@code buf}. Returns
205      * the number of characters actually read or -1 if the end of the reader has
206      * been reached. The bytes are either obtained from converting bytes in this
207      * reader's buffer or by first filling the buffer from the source
208      * InputStream and then reading from the buffer.
209      * 
210      * @param buf
211      *            the array to store the characters read.
212      * @param offset
213      *            the initial position in {@code buf} to store the characters
214      *            read from this reader.
215      * @param length
216      *            the maximum number of characters to read.
217      * @return the number of characters read or -1 if the end of the reader has
218      *         been reached.
219      * @throws IndexOutOfBoundsException
220      *             if {@code offset < 0} or {@code length < 0}, or if
221      *             {@code offset + length} is greater than the length of
222      *             {@code buf}.
223      * @throws IOException
224      *             if this reader is closed or some other I/O error occurs.
225      */
226     @Override
227     public int read(char[] buf, int offset, int length) throws IOException {
228         synchronized (lock) {
229             if (!isOpen()) {
230                 throw new IOException("InputStreamReader is closed.");
231             }
232             if (offset < 0 || offset > buf.length - length || length < 0) {
233                 throw new IndexOutOfBoundsException();
234             }
235             if (length == 0) {
236                 return 0;
237             }
238 
239             CharBuffer out = CharBuffer.wrap(buf, offset, length);
240             CoderResult result = CoderResult.UNDERFLOW;
241 
242             // bytes.remaining() indicates number of bytes in buffer
243             // when 1-st time entered, it'll be equal to zero
244             boolean needInput = !bytes.hasRemaining();
245 
246             while (out.hasRemaining()) {
247                 // fill the buffer if needed
248                 if (needInput) {
249                     try {
250                         if ((in.available() == 0) 
251                             && (out.position() > offset)) {
252                             // we could return the result without blocking read
253                             break;
254                         }
255                     } catch (IOException e) {
256                         // available didn't work so just try the read
257                     }
258 
259                     int to_read = bytes.capacity() - bytes.limit();
260                     int off = bytes.arrayOffset() + bytes.limit();
261                     int was_red = in.read(bytes.array(), off, to_read);
262 
263                     if (was_red == -1) {
264                         endOfInput = true;
265                         break;
266                     } else if (was_red == 0) {
267                         break;
268                     }
269                     bytes.limit(bytes.limit() + was_red);
270                     needInput = false;
271                 }
272 
273                 // decode bytes
274                 result = decoder.decode(bytes, out, false);
275 
276                 if (result.isUnderflow()) {
277                     // compact the buffer if no space left
278                     if (bytes.limit() == bytes.capacity()) {
279                         bytes.compact();
280                         bytes.limit(bytes.position());
281                         bytes.position(0);
282                     }
283                     needInput = true;
284                 } else {
285                     break;
286                 }
287             }
288 
289             if (result == CoderResult.UNDERFLOW && endOfInput) {
290                 result = decoder.decode(bytes, out, true);
291                 decoder.flush(out);
292                 decoder.reset();
293             }
294             if (result.isMalformed()) {
295                 throw new MalformedInputException(result.length());
296             } else if (result.isUnmappable()) {
297                 throw new UnmappableCharacterException(result.length());
298             }
299 
300             return out.position() - offset == 0 ? -1 : out.position() - offset;
301         }
302     }
303 
304     /*
305      * Answer a boolean indicating whether or not this InputStreamReader is
306      * open.
307      */
308     private boolean isOpen() {
309         return in != null;
310     }
311 
312     /**
313      * Indicates whether this reader is ready to be read without blocking. If
314      * the result is {@code true}, the next {@code read()} will not block. If
315      * the result is {@code false} then this reader may or may not block when
316      * {@code read()} is called. This implementation returns {@code true} if
317      * there are bytes available in the buffer or the source stream has bytes
318      * available.
319      * 
320      * @return {@code true} if the receiver will not block when {@code read()}
321      *         is called, {@code false} if unknown or blocking will occur.
322      * @throws IOException
323      *             if this reader is closed or some other I/O error occurs.
324      */
325     @Override
326     public boolean ready() throws IOException {
327         synchronized (lock) {
328             if (in == null) {
329                 throw new IOException("InputStreamReader is closed.");
330             }
331             try {
332                 return bytes.hasRemaining() || in.available() > 0;
333             } catch (IOException e) {
334                 return false;
335             }
336         }
337     }
338 }