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 enc}. 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 enc 71 * identifies the character converter to use. 72 * @throws NullPointerException 73 * if {@code enc} is {@code null}. 74 * @throws UnsupportedEncodingException 75 * if the encoding specified by {@code enc} cannot be found. 76 */ 77 public InputStreamReader(InputStream in, final String enc) 78 throws UnsupportedEncodingException { 79 super(in); 80 if (enc == null) { 81 throw new NullPointerException(); 82 } 83 this.in = in; 84 try { 85 decoder = Charset.forName(enc).newDecoder().onMalformedInput( 86 CodingErrorAction.REPLACE).onUnmappableCharacter( 87 CodingErrorAction.REPLACE); 88 } catch (IllegalArgumentException e) { 89 throw (UnsupportedEncodingException) 90 new UnsupportedEncodingException(enc).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 historical 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 HistoricalCharsetNames.get(decoder.charset()); 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 at most {@code length} characters from this reader and stores them 189 * at position {@code offset} in the character array {@code buf}. 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 * @param buffer 196 * the array to store the characters read. 197 * @param offset 198 * the initial position in {@code buf} to store the characters 199 * read from this reader. 200 * @param length 201 * the maximum number of characters to read. 202 * @return the number of characters read or -1 if the end of the reader has 203 * been reached. 204 * @throws IndexOutOfBoundsException 205 * if {@code offset < 0} or {@code length < 0}, or if 206 * {@code offset + length} is greater than the length of 207 * {@code buf}. 208 * @throws IOException 209 * if this reader is closed or some other I/O error occurs. 210 */ 211 @Override 212 public int read(char[] buffer, int offset, int length) throws IOException { 213 synchronized (lock) { 214 if (!isOpen()) { 215 throw new IOException("InputStreamReader is closed"); 216 } 217 218 Arrays.checkOffsetAndCount(buffer.length, offset, length); 219 if (length == 0) { 220 return 0; 221 } 222 223 CharBuffer out = CharBuffer.wrap(buffer, offset, length); 224 CoderResult result = CoderResult.UNDERFLOW; 225 226 // bytes.remaining() indicates number of bytes in buffer 227 // when 1-st time entered, it'll be equal to zero 228 boolean needInput = !bytes.hasRemaining(); 229 230 while (out.hasRemaining()) { 231 // fill the buffer if needed 232 if (needInput) { 233 try { 234 if (in.available() == 0 && out.position() > offset) { 235 // we could return the result without blocking read 236 break; 237 } 238 } catch (IOException e) { 239 // available didn't work so just try the read 240 } 241 242 int desiredByteCount = bytes.capacity() - bytes.limit(); 243 int off = bytes.arrayOffset() + bytes.limit(); 244 int actualByteCount = in.read(bytes.array(), off, desiredByteCount); 245 246 if (actualByteCount == -1) { 247 endOfInput = true; 248 break; 249 } else if (actualByteCount == 0) { 250 break; 251 } 252 bytes.limit(bytes.limit() + actualByteCount); 253 needInput = false; 254 } 255 256 // decode bytes 257 result = decoder.decode(bytes, out, false); 258 259 if (result.isUnderflow()) { 260 // compact the buffer if no space left 261 if (bytes.limit() == bytes.capacity()) { 262 bytes.compact(); 263 bytes.limit(bytes.position()); 264 bytes.position(0); 265 } 266 needInput = true; 267 } else { 268 break; 269 } 270 } 271 272 if (result == CoderResult.UNDERFLOW && endOfInput) { 273 result = decoder.decode(bytes, out, true); 274 decoder.flush(out); 275 decoder.reset(); 276 } 277 if (result.isMalformed() || result.isUnmappable()) { 278 result.throwException(); 279 } 280 281 return out.position() - offset == 0 ? -1 : out.position() - offset; 282 } 283 } 284 285 private boolean isOpen() { 286 return in != null; 287 } 288 289 /** 290 * Indicates whether this reader is ready to be read without blocking. If 291 * the result is {@code true}, the next {@code read()} will not block. If 292 * the result is {@code false} then this reader may or may not block when 293 * {@code read()} is called. This implementation returns {@code true} if 294 * there are bytes available in the buffer or the source stream has bytes 295 * available. 296 * 297 * @return {@code true} if the receiver will not block when {@code read()} 298 * is called, {@code false} if unknown or blocking will occur. 299 * @throws IOException 300 * if this reader is closed or some other I/O error occurs. 301 */ 302 @Override 303 public boolean ready() throws IOException { 304 synchronized (lock) { 305 if (in == null) { 306 throw new IOException("InputStreamReader is closed"); 307 } 308 try { 309 return bytes.hasRemaining() || in.available() > 0; 310 } catch (IOException e) { 311 return false; 312 } 313 } 314 } 315 } 316