001    package 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    
017    import java.io.IOException;
018    import 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     */
027    public 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    }