# ReversibleReader.java

    /*
    ** Module   : ReversibleReader.java
    ** Abstract : a reader which allows its input to be unread
    **
    ** Copyright (c) 2008-2017, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
    ** 001 GES 20081015 NEW   @40316 First version which provides a reader which
    **                               can unread its input. This is similar in
    **                               function to the J2SE PushbackReader but it
    **                               is designed with the ability for an external
    **                               user to inspect and obtain unread data
    **                               separately from data that has not yet been
    **                               read.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.io;

    import java.io.*;
    import java.util.*;

    /**
     * Implements a reader can unread its input. This is similar in function to
     * the J2SE PushbackReader but it is designed with the ability for an external
     * user to inspect and obtain unread data separately from data that has not
     * yet been read.
     */
    public class ReversibleReader
    extends FilterReader
    {
       /** Number of elements by which to grow or shrink the array. */
       private static final int INCREMENT = 1024;

       /** Number of empty elements at which the array will be shrunk. */
       private static final int THRESHOLD = INCREMENT * 2;

       /** LIFO queue for data that has been "unread". */
       private char[] queue = null;

       /** Index of the "front" of the queue (the next char to be read). */
       private int idx = -1;

       /**
        * Creates an instance with the specified reader as the primary data
        * source.
        *
        * @param    reader
        *           The primary data source.
        */
       public ReversibleReader(Reader reader)
       {
          super(reader);
       }

       /**
        * Queue the given data on a last-in first-out (LIFO) basis.
        *
        * @param   ch
        *          The character to be unread.
        */
       public void unread(int ch)
       {
          synchronized (lock)
          {
             // allocate space as needed for 1 character
             grow(1);

             // copy the character into the array and maintain the index
             queue[++idx] = (char) ch;
          }
       }

       /**
        * Queue the given data on a last-in first-out (LIFO) basis.
        *
        * @param   buf
        *          The data to be unread.
        */
       public void unread(char[] buf)
       {
          synchronized (lock)
          {
             // allocate space as needed
             grow(buf.length);

             // copy the data into the array (we must reverse the order of
             // the data to maintain a proper LIFO)
             for (int i = buf.length - 1; i >= 0; i--)
             {
                queue[++idx] = buf[i];
             }
          }
       }

       /**
        * Reports on the current amount of queued data.  Queued data is that
        * which has been explicitly {@link #unread} AND which also has not yet
        * been re-read using {@link #read}.
        *
        * @return   The size of the queue.
        */
       public int queueSize()
       {
          synchronized (lock)
          {
             return idx + 1;
          }
       }

       /**
        * Obtain the current contents of the queued (unread) data and clear
        * the queue completely.
        *
        * @return   All queued data or <code>null</code> if there is no queued
        *           data.
        */
       public char[] clear()
       {
          char[] buffer = null;

          synchronized (lock)
          {
             // check if there is a queue allocated
             if (queue != null)
             {
                // check if there is data in the queue
                if (idx >= 0)
                {
                   int sz = idx + 1;

                   buffer = new char[sz];

                   // copy the data into the array (we must reverse the order of
                   // the data to convert back from LIFO order)
                   for (int i = 0; i < sz; i++)
                   {
                      buffer[i] = queue[idx--];
                   }
                }

                // clear the queue
                queue = null;
                idx   = -1;
             }
          }

          return buffer;
       }

       /**
        * Reads the next byte from the current stream. If no stream was
        * switched to, returns <code>-1</code> as the indication of the
        * <code>EOF</code> condition.
        *
        * @return   The character read, as an integer in the range 0 to 65535
        *           (0x0000-0xFFFF), or -1 if the end of the stream has been
        *           reached or if no reader was switched to.
        *
        * @throws   IOException
        *           If any error occurs in reading from the underlying reader.
        */
       public int read()
       throws IOException
       {
          int ch = -1;

          synchronized (lock)
          {
             if (queue != null && idx >= 0)
             {
                // read queued data and maintain index
                ch = queue[idx--];

                // reduce memory usage if we have too much space
                shrink();
             }
             else
             {
                // read from the stream
                if (super.in != null)
                {
                   ch = super.read();
                }
             }
          }

          return ch;
       }

       /**
        * Reads characters into a portion of an array.
        *
        * @param    buf
        *           Destination buffer
        * @param    off
        *           Offset at which to start writing characters
        * @param    len
        *           Maximum number of characters to read
        *
        * @return   The number of characters read, or -1 if the end of the
        *           stream has been reached
        *
        * @throws   IOException
        *           If an I/O error occurs
        */
       public int read(char buf[], int off, int len)
       throws IOException
       {
          int num = 0;

          if (len > 0)
          {
             synchronized (lock)
             {
                if (off < 0 || off > buf.length)
                {
                   throw new IndexOutOfBoundsException("Invalid offset " + off);
                }

                if (off + len > buf.length)
                {
                   // limit max number of read chars to the space available
                   len = buf.length - off;
                }

                int remain = len;

                if (queue != null && idx >= 0)
                {
                   int start  = 0;
                   int amount = 0;

                   int sz = idx + 1;

                   if (remain <= sz)
                   {
                      // there is enough queued data to satisfy the request
                      start  = sz - remain;
                      amount = remain;
                   }
                   else
                   {
                      // we can only partially satisfy the request so copy
                      // everything
                      start  = 0;
                      amount = sz;
                   }

                   // read queued data
                   System.arraycopy(queue, start, buf, off, amount);

                   // maintain the offset
                   off += amount;

                   // reduce the amount of additional data needed
                   remain -= amount;

                   // remember that we read some data
                   num = amount;

                   // maintain index
                   idx -= amount;

                   if (idx < 0)
                   {
                      queue = null;
                      idx   = -1;
                   }
                   else
                   {
                      // reduce memory usage if we have too much space
                      shrink();
                   }
                }

                if (remain > 0)
                {
                   // read from the stream
                   int num2 = super.read(buf, off, remain);

                   if (num2 == -1)
                   {
                      // EOF from the backing stream
                      if (num == 0)
                      {
                         // we didn't read any data so return EOF
                         num = -1;
                      }
                   }
                   else
                   {
                      // we read from the stream
                      num += num2;
                   }
                }
             }
          }

          return num;
       }

       /**
        * Tells whether this stream is ready to be read.
        *
        * @return   <code>true</code> if a read request will not block.
        *
        * @throws   IOException
        *           If an I/O error occurs
        */
       public boolean ready()
       throws IOException
       {
          synchronized (lock)
          {
             return (idx >= 0 || super.ready());
          }
       }

       /**
        * Closes the stream and releases any system resources associated with
        * it.
        * <p>
        * Closing a previously closed stream has no effect.
        *
        * @throws   IOException
        *           If an I/O error occurs
        */
       public void close()
       throws IOException
       {
          synchronized (lock)
          {
             queue = null;
             idx   = -1;
             super.close();
          }
       }

       /**
        * Grow the array by the standard increment amount if it is full. This
        * doesn't change the number of unused elements.
        *
        * @param    amount
        *           The amount of space needed.
        */
       private void grow(int amount)
       {
          if (queue == null)
          {
             // allocate a new array when needed
             queue = new char[nextIncrement(amount)];
             idx   = -1;
          }
          else
          {
             int newsize = idx + 1 + amount;

             // is there room?
             if (newsize >= queue.length)
             {
                // reallocate the array to a size sufficient to hold the
                // expected contents AND copy the current contents
                queue = Arrays.copyOf(queue, nextIncrement(newsize));
             }
          }
       }

       /**
        * Shrink the array to the smallest multiple of the standard increment
        * amount if there is enough space to reclaim while still leaving some
        * open slots (so new additions won't cause array growth immediately).
        * This doesn't change the number of unused elements.
        */
       private void shrink()
       {
          int size = idx + 1;

          if (size < (queue.length - THRESHOLD))
          {
             queue = Arrays.copyOf(queue, nextIncrement(size));
          }
       }

       /**
        * Calculate the next multiple of INCREMENT that can hold the given
        * <code>size</code> elements.
        *
        * @param    size
        *           The amount of space needed.
        *
        * @return   The nearest multiple of INCREMENT that is larger than the
        *           given <code>size</code>.
        */
       private int nextIncrement(int size)
       {
          return ((size / INCREMENT) + 1) * INCREMENT;
       }
    }