View Javadoc
1   package net.sf.logdistiller.util;
2   
3   /*
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  import java.io.IOException;
18  import java.io.Reader;
19  
20  /**
21   * A reader reading its content from another reader, but buffering the data to be able to give it back on demand. When
22   * data is given back, it is not maintained in memory any more. When using this class, take care to get data regularly,
23   * or all the data read will stay in memory, risking OutOfMemoryException !
24   *
25   * @see #freeData
26   */
27  public class BufferingReader
28      extends Reader
29  {
30      private int bufferBlockSize;
31  
32      private char[] buffer;
33  
34      private int begin = 0;
35  
36      private int end = 0;
37  
38      private final Reader reader;
39  
40      public BufferingReader( Reader reader, int bufferBlockSize )
41      {
42          this.reader = reader;
43          this.bufferBlockSize = bufferBlockSize;
44          buffer = new char[bufferBlockSize];
45      }
46  
47      public BufferingReader( Reader reader )
48      {
49          this( reader, 16384 );
50      }
51  
52      public boolean ready()
53          throws IOException
54      {
55          return reader.ready();
56      }
57  
58      public void close()
59          throws IOException
60      {
61          reader.close();
62      }
63  
64      public int read( char[] cbuf, int off, int len )
65          throws IOException
66      {
67          int count = reader.read( cbuf, off, len );
68          if ( count > 0 )
69          {
70              if ( end + count >= buffer.length )
71              {
72                  prepareBuffer( count + ( end - begin ) );
73              }
74              System.arraycopy( cbuf, off, buffer, end, count );
75              end += count;
76          }
77          return count;
78      }
79  
80      /**
81       * Get the length of data available in the buffer.
82       *
83       * @return available data length
84       */
85      public int getDataLength()
86      {
87          return ( end - begin );
88      }
89  
90      /**
91       * Get some data that have been buffered, and free corresponding space in the buffer.
92       *
93       * @param count the number of characters to get
94       * @return the corresponding data
95       * @throws IndexOutOfBoundsException if the amount of data asked is more than what is available in the buffer
96       * @see #getDataLength()
97       */
98      public String freeData( int count )
99      {
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 }