001package net.sf.logdistiller.util;
002
003/*
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017import java.io.IOException;
018import java.io.Reader;
019
020/**
021 * A reader reading its content from another reader, but buffering the data to be able to give it back on demand. When
022 * data is given back, it is not maintained in memory any more. When using this class, take care to get data regularly,
023 * or all the data read will stay in memory, risking OutOfMemoryException !
024 *
025 * @see #freeData
026 */
027public class BufferingReader
028    extends Reader
029{
030    private int bufferBlockSize;
031
032    private char[] buffer;
033
034    private int begin = 0;
035
036    private int end = 0;
037
038    private final Reader reader;
039
040    public BufferingReader( Reader reader, int bufferBlockSize )
041    {
042        this.reader = reader;
043        this.bufferBlockSize = bufferBlockSize;
044        buffer = new char[bufferBlockSize];
045    }
046
047    public BufferingReader( Reader reader )
048    {
049        this( reader, 16384 );
050    }
051
052    public boolean ready()
053        throws IOException
054    {
055        return reader.ready();
056    }
057
058    public void close()
059        throws IOException
060    {
061        reader.close();
062    }
063
064    public int read( char[] cbuf, int off, int len )
065        throws IOException
066    {
067        int count = reader.read( cbuf, off, len );
068        if ( count > 0 )
069        {
070            if ( end + count >= buffer.length )
071            {
072                prepareBuffer( count + ( end - begin ) );
073            }
074            System.arraycopy( cbuf, off, buffer, end, count );
075            end += count;
076        }
077        return count;
078    }
079
080    /**
081     * Get the length of data available in the buffer.
082     *
083     * @return available data length
084     */
085    public int getDataLength()
086    {
087        return ( end - begin );
088    }
089
090    /**
091     * Get some data that have been buffered, and free corresponding space in the buffer.
092     *
093     * @param count the number of characters to get
094     * @return the corresponding data
095     * @throws IndexOutOfBoundsException if the amount of data asked is more than what is available in the buffer
096     * @see #getDataLength()
097     */
098    public String freeData( int count )
099    {
100        if ( count > getDataLength() )
101        {
102            throw new IndexOutOfBoundsException( "asked for " + count + " chars, but buffer contains only "
103                + getDataLength() + " chars." );
104        }
105        String data = new String( buffer, begin, count );
106        begin += count;
107        return data;
108    }
109
110    /**
111     * Get the size of the buffer.
112     *
113     * @return the buffer size
114     */
115    public int getBufferSize()
116    {
117        return buffer.length;
118    }
119
120    /**
121     * Shrink the buffer to its minimal size to store actual data. In normal use, the buffer grows as needed but never
122     * shrinks.
123     */
124    public void shrinkBuffer()
125    {
126        int size = calculateBufferSize( end - begin );
127        if ( size < buffer.length )
128        {
129            char[] oldBuffer = buffer;
130            buffer = new char[size];
131            end -= begin;
132            System.arraycopy( oldBuffer, begin, buffer, 0, end );
133            begin = 0;
134        }
135    }
136
137    /**
138     * prepare the internal buffer to insert some data. Existing data in the buffer are moved to the beginning of the
139     * buffer (so that begin = 0), and a new buffer is reallocated if its length was not sufficient.
140     *
141     * @param size the minimal size of the buffer
142     */
143    private void prepareBuffer( int size )
144    {
145        char[] oldBuffer = buffer;
146        if ( size > buffer.length )
147        {
148            // buffer is not big enough: must enlarge (to a multiple of bufferBlockSize)
149            buffer = new char[calculateBufferSize( size )];
150        }
151        end -= begin;
152        System.arraycopy( oldBuffer, begin, buffer, 0, end );
153        begin = 0;
154    }
155
156    private int calculateBufferSize( int size )
157    {
158        return bufferBlockSize * ( size / bufferBlockSize + 1 );
159    }
160}