All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.inputConsole.posix.InputStreamReader Maven / Gradle / Ivy

The newest version!
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package dorkbox.inputConsole.posix;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;


/**
 * NOTE for JLine: the default InputStreamReader that comes from the JRE usually read more bytes than needed from the input stream, which is
 * not usable in a character per character model used in the console. We thus use the harmony code which only reads the minimal number of
 * bytes, with a modification to ensure we can read larger characters (UTF-16 has up to 4 bytes, and UTF-32, rare as it is, may have up to
 * 8).
 *
 *
 * A class for turning a byte stream into a character stream. Data read from the source input stream is converted into characters by either
 * a default or a provided character converter. The default encoding is taken from the "file.encoding" system property.
 * {@code InputStreamReader} contains a buffer of bytes read from the source stream and converts these into characters as needed. The buffer
 * size is 8K.
 *
 * @see OutputStreamWriter
 */
@SuppressWarnings("ALL")
class InputStreamReader extends Reader {

    private InputStream in;

    private static final int BUFFER_SIZE = 8192;

    private boolean endOfInput = false;

    String encoding;

    CharsetDecoder decoder;

    ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE);

    /**
     * Constructs a new {@code InputStreamReader} on the {@link InputStream} {@code in}. This constructor sets the character converter to
     * the encoding specified in the "file.encoding" property and falls back to ISO 8859_1 (ISO-Latin-1) if the property doesn't exist.
     *
     * @param in the input stream from which to read characters.
     */
    public InputStreamReader(InputStream in) {
        super(in);
        this.in = in;
        // FIXME: This should probably use Configuration.getFileEncoding()
        this.encoding = System.getProperty("file.encoding", "ISO8859_1"); //$NON-NLS-1$//$NON-NLS-2$
        this.decoder =
                        Charset.forName(this.encoding).newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
                                        .onUnmappableCharacter(CodingErrorAction.REPLACE);
        this.bytes.limit(0);
    }

    /**
     * Constructs a new InputStreamReader on the InputStream {@code in}. The character converter that is used to decode bytes into
     * characters is identified by name by {@code enc}. If the encoding cannot be found, an UnsupportedEncodingException error is thrown.
     *
     * @param in the InputStream from which to read characters.
     * @param enc identifies the character converter to use.
     * @throws NullPointerException if {@code enc} is {@code null}.
     * @throws UnsupportedEncodingException if the encoding specified by {@code enc} cannot be found.
     */
    public InputStreamReader(InputStream in, final String enc)
    // throws UnsupportedEncodingException
    {
        super(in);

        if (enc == null) {
            throw new NullPointerException();
        }
        this.in = in;

        try {
            this.decoder =
                            Charset.forName(enc).newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
                                            .onUnmappableCharacter(CodingErrorAction.REPLACE);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            // throw (UnsupportedEncodingException)
            // new UnsupportedEncodingException(enc).initCause(e);
        }
        this.bytes.limit(0);
    }

    /**
     * Constructs a new InputStreamReader on the InputStream {@code in} and CharsetDecoder {@code dec}.
     *
     * @param in the source InputStream from which to read characters.
     * @param dec the CharsetDecoder used by the character conversion.
     */
    public InputStreamReader(InputStream in, CharsetDecoder dec) {
        super(in);
        dec.averageCharsPerByte();
        this.in = in;
        this.decoder = dec;
        this.bytes.limit(0);
    }

    /**
     * Constructs a new InputStreamReader on the InputStream {@code in} and Charset {@code charset}.
     *
     * @param in the source InputStream from which to read characters.
     * @param charset the Charset that defines the character converter
     */
    public InputStreamReader(InputStream in, Charset charset) {
        super(in);
        this.in = in;
        this.decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE);
        this.bytes.limit(0);
    }

    /**
     * Closes this reader. This implementation closes the source InputStream and releases all local storage.
     *
     * @throws IOException if an error occurs attempting to close this reader.
     */
    @Override
    public void close() throws IOException {
        synchronized (this.lock) {
            this.decoder = null;
            if (this.in != null) {
                this.in.close();
                this.in = null;
            }
        }
    }

    /**
     * Returns the name of the encoding used to convert bytes into characters. The value {@code null} is returned if this reader has been
     * closed.
     *
     * @return the name of the character converter or {@code null} if this reader is closed.
     */
    public String getEncoding() {
        if (!isOpen()) {
            return null;
        }
        return this.encoding;
    }

    /**
     * Reads a single character from this reader and returns it as an integer with the two higher-order bytes set to 0. Returns -1 if the
     * end of the reader has been reached. The byte value is either obtained from converting bytes in this reader's buffer or by first
     * filling the buffer from the source InputStream and then reading from the buffer.
     *
     * @return the character read or -1 if the end of the reader has been reached.
     * @throws IOException if this reader is closed or some other I/O error occurs.
     */
    @Override
    public int read() throws IOException {
        synchronized (this.lock) {
            if (!isOpen()) {
                throw new IOException("InputStreamReader is closed.");
            }

            char buf[] = new char[4];
            return read(buf, 0, 4) != -1 ? Character.codePointAt(buf, 0) : -1;
        }
    }

    /**
     * Reads at most {@code length} characters from this reader and stores them at position {@code offset} in the character array
     * {@code buf}. Returns the number of characters actually read or -1 if the end of the reader has been reached. The bytes are either
     * obtained from converting bytes in this reader's buffer or by first filling the buffer from the source InputStream and then reading
     * from the buffer.
     *
     * @param buf the array to store the characters read.
     * @param offset the initial position in {@code buf} to store the characters read from this reader.
     * @param length the maximum number of characters to read.
     * @return the number of characters read or -1 if the end of the reader has been reached.
     * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code offset + length} is greater than the
     *         length of {@code buf}.
     * @throws IOException if this reader is closed or some other I/O error occurs.
     */
    @Override
    public int read(char[] buf, int offset, int length) throws IOException {
        synchronized (this.lock) {
            if (!isOpen()) {
                throw new IOException("InputStreamReader is closed.");
            }
            if (offset < 0 || offset > buf.length - length || length < 0) {
                throw new IndexOutOfBoundsException();
            }
            if (length == 0) {
                return 0;
            }

            CharBuffer out = CharBuffer.wrap(buf, offset, length);
            CoderResult result = CoderResult.UNDERFLOW;

            // bytes.remaining() indicates number of bytes in buffer
            // when 1-st time entered, it'll be equal to zero
            boolean needInput = !this.bytes.hasRemaining();

            while (out.hasRemaining()) {
                // fill the buffer if needed
                if (needInput) {
                    try {
                        if (this.in.available() == 0 && out.position() > offset) {
                            // we could return the result without blocking read
                            break;
                        }
                    } catch (IOException e) {
                        // available didn't work so just try the read
                    }

                    int to_read = this.bytes.capacity() - this.bytes.limit();
                    int off = this.bytes.arrayOffset() + this.bytes.limit();
                    int was_red = this.in.read(this.bytes.array(), off, to_read);

                    if (was_red == -1) {
                        this.endOfInput = true;
                        break;
                    } else if (was_red == 0) {
                        break;
                    }
                    this.bytes.limit(this.bytes.limit() + was_red);
                    needInput = false;
                }

                // decode bytes
                result = this.decoder.decode(this.bytes, out, false);

                if (result.isUnderflow()) {
                    // compact the buffer if no space left
                    if (this.bytes.limit() == this.bytes.capacity()) {
                        this.bytes.compact();
                        this.bytes.limit(this.bytes.position());
                        this.bytes.position(0);
                    }
                    needInput = true;
                } else {
                    break;
                }
            }

            if (result == CoderResult.UNDERFLOW && this.endOfInput) {
                result = this.decoder.decode(this.bytes, out, true);
                this.decoder.flush(out);
                this.decoder.reset();
            }
            if (result.isMalformed()) {
                throw new MalformedInputException(result.length());
            } else if (result.isUnmappable()) {
                throw new UnmappableCharacterException(result.length());
            }

            return out.position() - offset == 0 ? -1 : out.position() - offset;
        }
    }

    /*
     * Answer a boolean indicating whether or not this InputStreamReader is open.
     */
    private boolean isOpen() {
        return this.in != null;
    }

    /**
     * Indicates whether this reader is ready to be read without blocking. If the result is {@code true}, the next {@code read()} will not
     * block. If the result is {@code false} then this reader may or may not block when {@code read()} is called. This implementation
     * returns {@code true} if there are bytes available in the buffer or the source stream has bytes available.
     *
     * @return {@code true} if the receiver will not block when {@code read()} is called, {@code false} if unknown or blocking will occur.
     * @throws IOException if this reader is closed or some other I/O error occurs.
     */
    @Override
    public boolean ready() throws IOException {
        synchronized (this.lock) {
            if (this.in == null) {
                throw new IOException("InputStreamReader is closed.");
            }
            try {
                return this.bytes.hasRemaining() || this.in.available() > 0;
            } catch (IOException e) {
                return false;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy