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 }