com.helger.commons.io.stream.NonBlockingPushbackInputStream Maven / Gradle / Ivy
Show all versions of ph-commons Show documentation
/*
* Copyright (C) 2014-2024 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed 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 com.helger.commons.io.stream;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import com.helger.commons.ValueEnforcer;
/**
* A non-synchronized copy of the class {@link java.io.PushbackInputStream}.
*
* @author Philip Helger
* @see java.io.PushbackInputStream
*/
public class NonBlockingPushbackInputStream extends WrappedInputStream
{
/**
* The pushback buffer.
*
* @since JDK1.1
*/
protected byte [] m_aBuf;
/**
* The position within the pushback buffer from which the next byte will be
* read. When the buffer is empty, pos
is equal to
* buf.length
; when the buffer is full, pos
is equal
* to zero.
*
* @since JDK1.1
*/
protected int m_nBufPos;
/**
* Creates a PushbackInputStream
with a pushback buffer of the
* specified size
, and saves its argument, the input stream
* in
, for later use. Initially, there is no pushed-back byte
* (the field pushBack
is initialized to -1
).
*
* @param aIS
* the input stream from which bytes will be read.
* @param nSize
* the size of the pushback buffer.
* @exception IllegalArgumentException
* if size is ≤ 0
* @since JDK1.1
*/
public NonBlockingPushbackInputStream (@Nonnull final InputStream aIS, @Nonnegative final int nSize)
{
super (aIS);
ValueEnforcer.isGT0 (nSize, "Size");
m_aBuf = new byte [nSize];
m_nBufPos = nSize;
}
/**
* Creates a PushbackInputStream
and saves its argument, the
* input stream in
, for later use. Initially, there is no
* pushed-back byte (the field pushBack
is initialized to
* -1
).
*
* @param aIS
* the input stream from which bytes will be read.
*/
public NonBlockingPushbackInputStream (@Nonnull final InputStream aIS)
{
this (aIS, 1);
}
/**
* Check to make sure that this stream has not been closed
*/
private void _ensureOpen () throws IOException
{
if (in == null)
throw new IOException ("Stream closed");
}
/**
* @return The number of bytes currently in the "unread" buffer.
*/
@Nonnegative
public int getUnreadCount ()
{
return m_aBuf.length - m_nBufPos;
}
/**
* @return true
if at least one "unread" byte is present.
*/
public boolean hasUnreadBytes ()
{
return m_nBufPos < m_aBuf.length;
}
/**
* Reads the next byte of data from this input stream. The value byte is
* returned as an int
in the range 0
to
* 255
. If no byte is available because the end of the stream has
* been reached, the value -1
is returned. This method blocks
* until input data is available, the end of the stream is detected, or an
* exception is thrown.
*
* This method returns the most recently pushed-back byte, if there is one,
* and otherwise calls the read
method of its underlying input
* stream and returns whatever value that method returns.
*
* @return the next byte of data, or -1
if the end of the stream
* has been reached.
* @exception IOException
* if this input stream has been closed by invoking its
* {@link #close()} method, or an I/O error occurs.
* @see java.io.InputStream#read()
*/
@Override
public int read () throws IOException
{
_ensureOpen ();
if (m_nBufPos < m_aBuf.length)
return m_aBuf[m_nBufPos++] & 0xff;
return super.read ();
}
/**
* Reads up to len
bytes of data from this input stream into an
* array of bytes. This method first reads any pushed-back bytes; after that,
* if fewer than len
bytes have been read then it reads from the
* underlying input stream. If len
is not zero, the method blocks
* until at least 1 byte of input is available; otherwise, no bytes are read
* and 0
is returned.
*
* @param aBuf
* the buffer into which the data is read.
* @param nOfs
* the start offset in the destination array b
* @param nLen
* the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or -1
* if there is no more data because the end of the stream has been
* reached.
* @exception NullPointerException
* If b
is null
.
* @exception IndexOutOfBoundsException
* If off
is negative, len
is negative,
* or len
is greater than b.length - off
* @exception IOException
* if this input stream has been closed by invoking its
* {@link #close()} method, or an I/O error occurs.
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read (@Nonnull final byte [] aBuf, @Nonnegative final int nOfs, @Nonnegative final int nLen) throws IOException
{
ValueEnforcer.isArrayOfsLen (aBuf, nOfs, nLen);
_ensureOpen ();
if (nLen == 0)
return 0;
int nRealOfs = nOfs;
int nRealLen = nLen;
int nBufAvail = m_aBuf.length - m_nBufPos;
if (nBufAvail > 0)
{
if (nRealLen < nBufAvail)
nBufAvail = nRealLen;
System.arraycopy (m_aBuf, m_nBufPos, aBuf, nRealOfs, nBufAvail);
m_nBufPos += nBufAvail;
nRealOfs += nBufAvail;
nRealLen -= nBufAvail;
}
if (nRealLen > 0)
{
nRealLen = super.read (aBuf, nRealOfs, nRealLen);
if (nRealLen == -1)
return nBufAvail == 0 ? -1 : nBufAvail;
return nBufAvail + nRealLen;
}
return nBufAvail;
}
/**
* Pushes back a byte by copying it to the front of the pushback buffer. After
* this method returns, the next byte to be read will have the value
* (byte)b
.
*
* @param b
* the int
value whose low-order byte is to be pushed
* back.
* @exception IOException
* If there is not enough room in the pushback buffer for the byte,
* or this input stream has been closed by invoking its
* {@link #close()} method.
*/
public void unread (final int b) throws IOException
{
_ensureOpen ();
if (m_nBufPos == 0)
throw new IOException ("Push back buffer is full");
m_aBuf[--m_nBufPos] = (byte) b;
}
/**
* Pushes back a portion of an array of bytes by copying it to the front of
* the pushback buffer. After this method returns, the next byte to be read
* will have the value b[off]
, the byte after that will have the
* value b[off+1]
, and so forth.
*
* @param aBuf
* the byte array to push back.
* @param nOfs
* the start offset of the data.
* @param nLen
* the number of bytes to push back.
* @exception IOException
* If there is not enough room in the pushback buffer for the
* specified number of bytes, or this input stream has been closed
* by invoking its {@link #close()} method.
* @since JDK1.1
*/
public void unread (@Nonnull final byte [] aBuf, @Nonnegative final int nOfs, @Nonnegative final int nLen) throws IOException
{
ValueEnforcer.isArrayOfsLen (aBuf, nOfs, nLen);
_ensureOpen ();
if (nLen > m_nBufPos)
throw new IOException ("Push back buffer is full");
m_nBufPos -= nLen;
System.arraycopy (aBuf, nOfs, m_aBuf, m_nBufPos, nLen);
}
/**
* Pushes back an array of bytes by copying it to the front of the pushback
* buffer. After this method returns, the next byte to be read will have the
* value b[0]
, the byte after that will have the value
* b[1]
, and so forth.
*
* @param aBuf
* the byte array to push back
* @exception IOException
* If there is not enough room in the pushback buffer for the
* specified number of bytes, or this input stream has been closed
* by invoking its {@link #close()} method.
* @since JDK1.1
*/
public void unread (@Nonnull final byte [] aBuf) throws IOException
{
unread (aBuf, 0, aBuf.length);
}
/**
* Returns an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking by the next invocation of a
* method for this input stream. The next invocation might be the same thread
* or another thread. A single read or skip of this many bytes will not block,
* but may read or skip fewer bytes.
*
* The method returns the sum of the number of bytes that have been pushed
* back and the value returned by {@link java.io.FilterInputStream#available
* available}.
*
* @return the number of bytes that can be read (or skipped over) from the
* input stream without blocking.
* @exception IOException
* if this input stream has been closed by invoking its
* {@link #close()} method, or an I/O error occurs.
* @see java.io.InputStream#available()
*/
@Override
public int available () throws IOException
{
_ensureOpen ();
return (m_aBuf.length - m_nBufPos) + super.available ();
}
/**
* Skips over and discards n
bytes of data from this input
* stream. The skip
method may, for a variety of reasons, end up
* skipping over some smaller number of bytes, possibly zero. If
* n
is negative, no bytes are skipped.
*
* The skip
method of PushbackInputStream
first
* skips over the bytes in the pushback buffer, if any. It then calls the
* skip
method of the underlying input stream if more bytes need
* to be skipped. The actual number of bytes skipped is returned.
*
* @param nSkip
* The number of bytes to skip. Must be ≥ 0.
* @return The number of bytes actually skipped
* @exception IOException
* if the stream does not support seek, or the stream has been
* closed by invoking its {@link #close()} method, or an I/O error
* occurs.
* @see java.io.InputStream#skip(long n)
* @since 1.2
*/
@Override
public long skip (final long nSkip) throws IOException
{
ValueEnforcer.isGE0 (nSkip, "SkipValue");
_ensureOpen ();
if (nSkip == 0)
return 0L;
long nRealSkip = nSkip;
final int nBufAvail = m_aBuf.length - m_nBufPos;
if (nBufAvail > 0)
{
if (nRealSkip <= nBufAvail)
{
m_nBufPos += (int) nRealSkip;
return nRealSkip;
}
m_nBufPos = m_aBuf.length;
nRealSkip -= nBufAvail;
}
return nBufAvail + super.skip (nRealSkip);
}
/**
* Tests if this input stream supports the mark
and
* reset
methods, which it does not.
*
* @return false
, since this class does not support the
* mark
and reset
methods.
* @see java.io.InputStream#mark(int)
* @see java.io.InputStream#reset()
*/
@Override
public boolean markSupported ()
{
return false;
}
/**
* Marks the current position in this input stream.
*
* The mark
method of PushbackInputStream
does
* nothing.
*
* @param readlimit
* the maximum limit of bytes that can be read before the mark position
* becomes invalid.
* @see java.io.InputStream#reset()
*/
@Override
@SuppressWarnings ("sync-override")
public void mark (final int readlimit)
{}
/**
* Repositions this stream to the position at the time the mark
* method was last called on this input stream.
*
* The method reset
for class PushbackInputStream
* does nothing except throw an IOException
.
*
* @exception IOException
* if this method is invoked.
* @see java.io.InputStream#mark(int)
* @see java.io.IOException
*/
@SuppressWarnings ("sync-override")
@Override
public void reset () throws IOException
{
throw new IOException ("mark/reset not supported");
}
/**
* Closes this input stream and releases any system resources associated with
* the stream. Once the stream has been closed, further read(), unread(),
* available(), reset(), or skip() invocations will throw an IOException.
* Closing a previously closed stream has no effect.
*
* @exception IOException
* if an I/O error occurs.
*/
@Override
public void close () throws IOException
{
if (in != null)
{
in.close ();
in = null;
m_aBuf = null;
}
}
}