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 }