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

com.phloc.commons.io.streams.NonBlockingBufferedReader Maven / Gradle / Ivy

There is a newer version: 5.0.0
Show newest version
/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[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.phloc.commons.io.streams;

import java.io.IOException;
import java.io.Reader;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.phloc.commons.ValueEnforcer;

/**
 * A non-synchronized copy of the class {@link java.io.BufferedReader}.
 * 
 * @author Philip Helger
 * @see java.io.BufferedReader
 */
public class NonBlockingBufferedReader extends Reader
{
  private static final int INVALIDATED = -2;
  private static final int UNMARKED = -1;

  private static final int DEFAULT_CHAR_BUFFER_SIZE = 8192;
  private static final int DEFAULT_EXPECTED_LINE_LENGTH = 80;

  private Reader m_aReader;
  private char [] m_aBuf;
  private int m_nChars;
  private int m_nNextCharIndex;

  private int m_nMarkedChar = UNMARKED;
  /** Valid only when markedChar > 0 */
  private int m_nReadAheadLimit = 0;

  /** If the next character is a line feed, skip it */
  private boolean m_bSkipLF = false;

  /** The skipLF flag when the mark was set */
  private boolean m_bMarkedSkipLF = false;

  /**
   * Creates a buffering character-input stream that uses a default-sized input
   * buffer.
   * 
   * @param aReader
   *        A Reader
   */
  public NonBlockingBufferedReader (@Nonnull final Reader aReader)
  {
    this (aReader, DEFAULT_CHAR_BUFFER_SIZE);
  }

  /**
   * Creates a buffering character-input stream that uses an input buffer of the
   * specified size.
   * 
   * @param aReader
   *        A Reader
   * @param nSize
   *        Input-buffer size
   * @exception IllegalArgumentException
   *            If size is <= 0
   */
  public NonBlockingBufferedReader (@Nonnull final Reader aReader, @Nonnegative final int nSize)
  {
    super (aReader);
    ValueEnforcer.isGT0 (nSize, "Size");
    this.m_aReader = aReader;
    this.m_aBuf = new char [nSize];
  }

  /**
   * Checks to make sure that the stream has not been closed
   * 
   * @throws IOException
   *         If the reader is not open
   */
  private void _ensureOpen () throws IOException
  {
    if (this.m_aReader == null)
      throw new IOException ("Stream closed");
  }

  /**
   * Fills the input buffer, taking the mark into account if it is valid.
   * 
   * @throws IOException
   *         In case reading fails
   */
  private void _fill () throws IOException
  {
    int nDstOfs = 0;
    if (this.m_nMarkedChar > UNMARKED)
    {
      // Marked
      final int nDelta = this.m_nNextCharIndex - this.m_nMarkedChar;
      if (nDelta >= this.m_nReadAheadLimit)
      {
        // Gone past read-ahead limit: Invalidate mark
        this.m_nMarkedChar = INVALIDATED;
        this.m_nReadAheadLimit = 0;
      }
      else
      {
        if (this.m_nReadAheadLimit <= this.m_aBuf.length)
        {
          // Shuffle in the current buffer
          System.arraycopy (this.m_aBuf, this.m_nMarkedChar, this.m_aBuf, 0, nDelta);
          this.m_nMarkedChar = 0;
          nDstOfs = nDelta;
        }
        else
        {
          // Reallocate buffer to accommodate read-ahead limit
          final char [] aNewBuf = new char [this.m_nReadAheadLimit];
          System.arraycopy (this.m_aBuf, this.m_nMarkedChar, aNewBuf, 0, nDelta);
          this.m_aBuf = aNewBuf;
          this.m_nMarkedChar = 0;
          nDstOfs = nDelta;
        }
        this.m_nNextCharIndex = nDelta;
        this.m_nChars = nDelta;
      }
    }

    int nBytesRead;
    do
    {
      nBytesRead = this.m_aReader.read (this.m_aBuf, nDstOfs, this.m_aBuf.length - nDstOfs);
    } while (nBytesRead == 0);
    if (nBytesRead > 0)
    {
      this.m_nChars = nDstOfs + nBytesRead;
      this.m_nNextCharIndex = nDstOfs;
    }
  }

  /**
   * Reads a single character.
   * 
   * @return The character read, as an integer in the range 0 to 65535 (
   *         0x00-0xffff), or -1 if the end of the stream has been
   *         reached
   * @exception IOException
   *            If an I/O error occurs
   */
  @Override
  public int read () throws IOException
  {
    _ensureOpen ();
    while (true)
    {
      if (this.m_nNextCharIndex >= this.m_nChars)
      {
        _fill ();
        if (this.m_nNextCharIndex >= this.m_nChars)
          return -1;
      }
      if (this.m_bSkipLF)
      {
        this.m_bSkipLF = false;
        if (this.m_aBuf[this.m_nNextCharIndex] == '\n')
        {
          this.m_nNextCharIndex++;
          continue;
        }
      }
      return this.m_aBuf[this.m_nNextCharIndex++];
    }
  }

  /**
   * Reads characters into a portion of an array, reading from the underlying
   * stream if necessary.
   * 
   * @param aBuf
   *        The buffer to be filled
   * @param nOfs
   *        The offset to start reading
   * @param nLen
   *        The number of bytes to read
   * @return The number of bytes read
   * @throws IOException
   *         in case reading fails
   */
  private int _internalRead (final char [] aBuf, final int nOfs, final int nLen) throws IOException
  {
    if (this.m_nNextCharIndex >= this.m_nChars)
    {
      /*
       * If the requested length is at least as large as the buffer, and if
       * there is no mark/reset activity, and if line feeds are not being
       * skipped, do not bother to copy the characters into the local buffer. In
       * this way buffered streams will cascade harmlessly.
       */
      if (nLen >= this.m_aBuf.length && this.m_nMarkedChar <= UNMARKED && !this.m_bSkipLF)
        return this.m_aReader.read (aBuf, nOfs, nLen);
      _fill ();
    }
    if (this.m_nNextCharIndex >= this.m_nChars)
      return -1;
    if (this.m_bSkipLF)
    {
      this.m_bSkipLF = false;
      if (this.m_aBuf[this.m_nNextCharIndex] == '\n')
      {
        this.m_nNextCharIndex++;
        if (this.m_nNextCharIndex >= this.m_nChars)
          _fill ();
        if (this.m_nNextCharIndex >= this.m_nChars)
          return -1;
      }
    }
    final int nBytesRead = Math.min (nLen, this.m_nChars - this.m_nNextCharIndex);
    System.arraycopy (this.m_aBuf, this.m_nNextCharIndex, aBuf, nOfs, nBytesRead);
    this.m_nNextCharIndex += nBytesRead;
    return nBytesRead;
  }

  /**
   * Reads characters into a portion of an array.
   * 

* This method implements the general contract of the corresponding * {@link Reader#read(char[], int, int) read} method of the * {@link Reader} class. As an additional convenience, it * attempts to read as many characters as possible by repeatedly invoking the * read method of the underlying stream. This iterated * read continues until one of the following conditions becomes * true: *

    *
  • The specified number of characters have been read, *
  • The read method of the underlying stream returns * -1, indicating end-of-file, or *
  • The ready method of the underlying stream returns * false, indicating that further input requests would block. *
* If the first read on the underlying stream returns * -1 to indicate end-of-file then this method returns * -1. Otherwise this method returns the number of characters * actually read. *

* Subclasses of this class are encouraged, but not required, to attempt to * read as many characters as possible in the same fashion. *

* Ordinarily this method takes characters from this stream's character * buffer, filling it from the underlying stream as necessary. If, however, * the buffer is empty, the mark is not valid, and the requested length is at * least as large as the buffer, then this method will read characters * directly from the underlying stream into the given array. Thus redundant * NonBlockingBufferedReaders will not copy data unnecessarily. * * @param cbuf * Destination buffer * @param nOfs * Offset at which to start storing characters * @param nLen * Maximum number of characters to read * @return The number of characters read, or -1 if the end of the stream has * been reached * @exception IOException * If an I/O error occurs */ @Override public int read (final char [] cbuf, final int nOfs, final int nLen) throws IOException { _ensureOpen (); ValueEnforcer.isArrayOfsLen (cbuf, nOfs, nLen); if (nLen == 0) return 0; // Main read int n = _internalRead (cbuf, nOfs, nLen); if (n <= 0) return n; while (n < nLen && this.m_aReader.ready ()) { final int n1 = _internalRead (cbuf, nOfs + n, nLen - n); if (n1 <= 0) break; n += n1; } return n; } /** * Reads a line of text. A line is considered to be terminated by any one of a * line feed ('\n'), a carriage return ('\r'), or a carriage return followed * immediately by a linefeed. * * @return A String containing the contents of the line, not including any * line-termination characters, or null if the end of the stream has * been reached * @exception IOException * If an I/O error occurs */ @Nullable public String readLine () throws IOException { StringBuilder aSB = null; int nStartChar; _ensureOpen (); boolean bOmitLF = this.m_bSkipLF; while (true) { if (this.m_nNextCharIndex >= this.m_nChars) _fill (); if (this.m_nNextCharIndex >= this.m_nChars) { /* EOF */ if (aSB != null && aSB.length () > 0) return aSB.toString (); return null; } boolean bEOL = false; char cLast = 0; int nIndex; /* Skip a leftover '\n', if necessary */ if (bOmitLF && this.m_aBuf[this.m_nNextCharIndex] == '\n') this.m_nNextCharIndex++; this.m_bSkipLF = false; bOmitLF = false; for (nIndex = this.m_nNextCharIndex; nIndex < this.m_nChars; nIndex++) { cLast = this.m_aBuf[nIndex]; if (cLast == '\n' || cLast == '\r') { bEOL = true; break; } } nStartChar = this.m_nNextCharIndex; this.m_nNextCharIndex = nIndex; if (bEOL) { String sStr; if (aSB == null) sStr = new String (this.m_aBuf, nStartChar, nIndex - nStartChar); else { aSB.append (this.m_aBuf, nStartChar, nIndex - nStartChar); sStr = aSB.toString (); } this.m_nNextCharIndex++; if (cLast == '\r') this.m_bSkipLF = true; return sStr; } if (aSB == null) aSB = new StringBuilder (DEFAULT_EXPECTED_LINE_LENGTH); aSB.append (this.m_aBuf, nStartChar, nIndex - nStartChar); } } /** * Skips characters. * * @param nBytes * The number of characters to skip * @return The number of characters actually skipped * @exception IllegalArgumentException * If n is negative. * @exception IOException * If an I/O error occurs */ @Override public long skip (final long nBytes) throws IOException { ValueEnforcer.isGE0 (nBytes, "Bytes"); _ensureOpen (); long nRest = nBytes; while (nRest > 0) { if (this.m_nNextCharIndex >= this.m_nChars) _fill (); if (this.m_nNextCharIndex >= this.m_nChars) { // EOF break; } if (this.m_bSkipLF) { this.m_bSkipLF = false; if (this.m_aBuf[this.m_nNextCharIndex] == '\n') this.m_nNextCharIndex++; } final int d = this.m_nChars - this.m_nNextCharIndex; if (nRest <= d) { this.m_nNextCharIndex += nRest; nRest = 0; break; } nRest -= d; this.m_nNextCharIndex = this.m_nChars; } return nBytes - nRest; } /** * Tells whether this stream is ready to be read. A buffered character stream * is ready if the buffer is not empty, or if the underlying character stream * is ready. * * @return true if the reader is ready * @exception IOException * If an I/O error occurs */ @Override public boolean ready () throws IOException { _ensureOpen (); /* * If newline needs to be skipped and the next char to be read is a newline * character, then just skip it right away. */ if (this.m_bSkipLF) { /* * Note that in.ready() will return true if and only if the next read on * the stream will not block. */ if (this.m_nNextCharIndex >= this.m_nChars && this.m_aReader.ready ()) _fill (); if (this.m_nNextCharIndex < this.m_nChars) { if (this.m_aBuf[this.m_nNextCharIndex] == '\n') this.m_nNextCharIndex++; this.m_bSkipLF = false; } } return this.m_nNextCharIndex < this.m_nChars || this.m_aReader.ready (); } /** * Tells whether this stream supports the mark() operation, which it does. * * @return Always true */ @Override public boolean markSupported () { return true; } /** * Marks the present position in the stream. Subsequent calls to reset() will * attempt to reposition the stream to this point. * * @param nReadAheadLimit * Limit on the number of characters that may be read while still * preserving the mark. An attempt to reset the stream after reading * characters up to this limit or beyond may fail. A limit value larger * than the size of the input buffer will cause a new buffer to be * allocated whose size is no smaller than limit. Therefore large * values should be used with care. * @exception IllegalArgumentException * If readAheadLimit is < 0 * @exception IOException * If an I/O error occurs */ @Override public void mark (@Nonnegative final int nReadAheadLimit) throws IOException { ValueEnforcer.isGE0 (nReadAheadLimit, "ReadAheadLimit"); _ensureOpen (); this.m_nReadAheadLimit = nReadAheadLimit; this.m_nMarkedChar = this.m_nNextCharIndex; this.m_bMarkedSkipLF = this.m_bSkipLF; } /** * Resets the stream to the most recent mark. * * @exception IOException * If the stream has never been marked, or if the mark has been * invalidated */ @Override public void reset () throws IOException { _ensureOpen (); if (this.m_nMarkedChar < 0) throw new IOException (this.m_nMarkedChar == INVALIDATED ? "Mark invalid" : "Stream not marked"); this.m_nNextCharIndex = this.m_nMarkedChar; this.m_bSkipLF = this.m_bMarkedSkipLF; } @Override public void close () throws IOException { if (this.m_aReader != null) { this.m_aReader.close (); this.m_aReader = null; this.m_aBuf = null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy