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 jdk.internal.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 }