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

org.jsoup.internal.SimpleBufferedInput Maven / Gradle / Ivy

The newest version!
package org.jsoup.internal;

import org.jsoup.helper.Validate;
import org.jspecify.annotations.Nullable;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import static org.jsoup.internal.SharedConstants.DefaultBufferSize;

/**
 A simple implemented of a buffered input stream, in which we can control the byte[] buffer to recycle it. Not safe for
 use between threads; no sync or locks. The buffer is borrowed on initial demand in fill.
 @since 1.18.2
 */
class SimpleBufferedInput extends FilterInputStream {
    static final int BufferSize = DefaultBufferSize;
    static final SoftPool BufferPool = new SoftPool<>(() -> new byte[BufferSize]);

    private byte @Nullable [] byteBuf; // the byte buffer; recycled via SoftPool. Created in fill if required
    private int bufPos;
    private int bufLength;
    private int bufMark = -1;
    private boolean inReadFully = false; // true when the underlying inputstream has been read fully

    SimpleBufferedInput(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        if (bufPos >= bufLength) {
            fill();
            if (bufPos >= bufLength)
                return -1;
        }
        return getBuf()[bufPos++] & 0xff;
    }

    @Override
    public int read(byte[] dest, int offset, int desiredLen) throws IOException {
        Validate.notNull(dest);
        if (offset < 0 || desiredLen < 0 || desiredLen > dest.length - offset) {
            throw new IndexOutOfBoundsException();
        } else if (desiredLen == 0) {
            return 0;
        }

        int bufAvail = bufLength - bufPos;
        if (bufAvail <= 0) { // can't serve from the buffer
            if (!inReadFully && bufMark < 0) {
                // skip creating / copying into a local buffer; just pass through
                int read = in.read(dest, offset, desiredLen);
                closeIfDone(read);
                return read;
            }
            fill();
            bufAvail = bufLength - bufPos;
        }

        int read = Math.min(bufAvail, desiredLen);
        if (read <= 0) {
            return -1;
        }

        System.arraycopy(getBuf(), bufPos, dest, offset, read);
        bufPos += read;
        return read;
    }

    private void fill() throws IOException {
        if (inReadFully) return;
        if (byteBuf == null) { // get one on first demand
            byteBuf = BufferPool.borrow();
        }

        if (bufMark < 0) { // no mark, can lose buffer (assumes we've read to bufLen)
            bufPos = 0;
        } else if (bufPos >= BufferSize) { // no room left in buffer
            if (bufMark > 0) { // can throw away early part of the buffer
                int size = bufPos - bufMark;
                System.arraycopy(byteBuf, bufMark, byteBuf, 0, size);
                bufPos = size;
                bufMark = 0;
            } else { // invalidate mark
                bufMark = -1;
                bufPos = 0;
            }
        }
        bufLength = bufPos;
        int read = in.read(byteBuf, bufPos, byteBuf.length - bufPos);
        if (read > 0) {
            bufLength = read + bufPos;
            while (byteBuf.length - bufLength > 0) { // read in more if we have space, without blocking
                if (in.available() < 1) break;
                read = in.read(byteBuf, bufLength, byteBuf.length - bufLength);
                if (read <= 0) break;
                bufLength += read;
            }
        }
        closeIfDone(read);
    }

    private void closeIfDone(int read) throws IOException {
        if (read == -1) {
            inReadFully = true;
            super.close(); // close underlying stream immediately; frees resources a little earlier
        }
    }

    byte[] getBuf() {
        Validate.notNull(byteBuf);
        return byteBuf;
    }

    /**
     Check if the underlying InputStream has been read fully. There may still content in this buffer to be consumed.
     @return true if the underlying inputstream has been read fully.
     */
    boolean baseReadFully() {
        return inReadFully;
    }

    @Override
    public int available() throws IOException {
        if (byteBuf != null && bufLength - bufPos > 0)
            return bufLength - bufPos; // doesn't include those in.available(), but mostly used as a block test
        return inReadFully ? 0 : in.available();
    }

    @SuppressWarnings("NonSynchronizedMethodOverridesSynchronizedMethod") // explicitly not synced
    @Override
    public void mark(int readlimit) {
        if (readlimit > BufferSize) {
            throw new IllegalArgumentException("Read-ahead limit is greater than buffer size");
        }
        bufMark = bufPos;
    }

    @SuppressWarnings("NonSynchronizedMethodOverridesSynchronizedMethod") // explicitly not synced
    @Override
    public void reset() throws IOException {
        if (bufMark < 0)
            throw new IOException("Resetting to invalid mark");
        bufPos = bufMark;
    }

    @Override
    public void close() throws IOException {
        super.close();
        if (byteBuf == null) return; // already closed, or never allocated
        BufferPool.release(byteBuf); // return the buffer to the pool
        byteBuf = null; // NPE further attempts to read
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy