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