View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.input.buffer;
18  
19  import java.util.Objects;
20  
21  import org.apache.commons.io.IOUtils;
22  
23  /**
24   * A buffer, which doesn't need reallocation of byte arrays, because it
25   * reuses a single byte array. This works particularly well, if reading
26   * from the buffer takes place at the same time than writing to. Such is the
27   * case, for example, when using the buffer within a filtering input stream,
28   * like the {@link CircularBufferInputStream}.
29   */
30  public class CircularByteBuffer {
31      private final byte[] buffer;
32      private int startOffset;
33      private int endOffset;
34      private int currentNumberOfBytes;
35  
36      /**
37       * Constructs a new instance with a reasonable default buffer size ({@link IOUtils#DEFAULT_BUFFER_SIZE}).
38       */
39      public CircularByteBuffer() {
40          this(IOUtils.DEFAULT_BUFFER_SIZE);
41      }
42  
43      /**
44       * Constructs a new instance with the given buffer size.
45       *
46       * @param size the size of buffer to create
47       */
48      public CircularByteBuffer(final int size) {
49          buffer = IOUtils.byteArray(size);
50          startOffset = 0;
51          endOffset = 0;
52          currentNumberOfBytes = 0;
53      }
54  
55      /**
56       * Adds a new byte to the buffer, which will eventually be returned by following
57       * invocations of {@link #read()}.
58       *
59       * @param value The byte, which is being added to the buffer.
60       * @throws IllegalStateException The buffer is full. Use {@link #hasSpace()},
61       *                               or {@link #getSpace()}, to prevent this exception.
62       */
63      public void add(final byte value) {
64          if (currentNumberOfBytes >= buffer.length) {
65              throw new IllegalStateException("No space available");
66          }
67          buffer[endOffset] = value;
68          ++currentNumberOfBytes;
69          if (++endOffset == buffer.length) {
70              endOffset = 0;
71          }
72      }
73  
74      /**
75       * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)}
76       * for the bytes at offsets {@code offset+0}, {@code offset+1}, ...,
77       * {@code offset+length-1} of byte array {@code targetBuffer}.
78       *
79       * @param targetBuffer the buffer to copy
80       * @param offset start offset
81       * @param length length to copy
82       * @throws IllegalStateException    The buffer doesn't have sufficient space. Use
83       *                                  {@link #getSpace()} to prevent this exception.
84       * @throws IllegalArgumentException Either of {@code offset}, or {@code length} is negative.
85       * @throws NullPointerException     The byte array {@code pBuffer} is null.
86       */
87      public void add(final byte[] targetBuffer, final int offset, final int length) {
88          Objects.requireNonNull(targetBuffer, "Buffer");
89          if (offset < 0 || offset >= targetBuffer.length) {
90              throw new IllegalArgumentException("Illegal offset: " + offset);
91          }
92          if (length < 0) {
93              throw new IllegalArgumentException("Illegal length: " + length);
94          }
95          if (currentNumberOfBytes + length > buffer.length) {
96              throw new IllegalStateException("No space available");
97          }
98          for (int i = 0; i < length; i++) {
99              buffer[endOffset] = targetBuffer[offset + i];
100             if (++endOffset == buffer.length) {
101                 endOffset = 0;
102             }
103         }
104         currentNumberOfBytes += length;
105     }
106 
107     /**
108      * Removes all bytes from the buffer.
109      */
110     public void clear() {
111         startOffset = 0;
112         endOffset = 0;
113         currentNumberOfBytes = 0;
114     }
115 
116     /**
117      * Returns the number of bytes, that are currently present in the buffer.
118      *
119      * @return the number of bytes
120      */
121     public int getCurrentNumberOfBytes() {
122         return currentNumberOfBytes;
123     }
124 
125     /**
126      * Returns the number of bytes, that can currently be added to the buffer.
127      *
128      * @return the number of bytes that can be added
129      */
130     public int getSpace() {
131         return buffer.length - currentNumberOfBytes;
132     }
133 
134     /**
135      * Returns, whether the buffer is currently holding, at least, a single byte.
136      *
137      * @return true if the buffer is not empty
138      */
139     public boolean hasBytes() {
140         return currentNumberOfBytes > 0;
141     }
142 
143     /**
144      * Returns, whether there is currently room for a single byte in the buffer.
145      * Same as {@link #hasSpace(int) hasSpace(1)}.
146      *
147      * @return true if there is space for a byte
148      * @see #hasSpace(int)
149      * @see #getSpace()
150      */
151     public boolean hasSpace() {
152         return currentNumberOfBytes < buffer.length;
153     }
154 
155     /**
156      * Returns, whether there is currently room for the given number of bytes in the buffer.
157      *
158      * @param count the byte count
159      * @return true if there is space for the given number of bytes
160      * @see #hasSpace()
161      * @see #getSpace()
162      */
163     public boolean hasSpace(final int count) {
164         return currentNumberOfBytes + count <= buffer.length;
165     }
166 
167     /**
168      * Returns, whether the next bytes in the buffer are exactly those, given by
169      * {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being
170      * removed from the buffer. If the result is true, then the following invocations
171      * of {@link #read()} are guaranteed to return exactly those bytes.
172      *
173      * @param sourceBuffer the buffer to compare against
174      * @param offset start offset
175      * @param length length to compare
176      * @return True, if the next invocations of {@link #read()} will return the
177      * bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ...,
178      * {@code pOffset}+{@code length}-1 of byte array {@code pBuffer}.
179      * @throws IllegalArgumentException Either of {@code pOffset}, or {@code length} is negative.
180      * @throws NullPointerException     The byte array {@code pBuffer} is null.
181      */
182     public boolean peek(final byte[] sourceBuffer, final int offset, final int length) {
183         Objects.requireNonNull(sourceBuffer, "Buffer");
184         if (offset < 0 || offset >= sourceBuffer.length) {
185             throw new IllegalArgumentException("Illegal offset: " + offset);
186         }
187         if (length < 0 || length > buffer.length) {
188             throw new IllegalArgumentException("Illegal length: " + length);
189         }
190         if (length < currentNumberOfBytes) {
191             return false;
192         }
193         int localOffset = startOffset;
194         for (int i = 0; i < length; i++) {
195             if (buffer[localOffset] != sourceBuffer[i + offset]) {
196                 return false;
197             }
198             if (++localOffset == buffer.length) {
199                 localOffset = 0;
200             }
201         }
202         return true;
203     }
204 
205     /**
206      * Returns the next byte from the buffer, removing it at the same time, so
207      * that following invocations won't return it again.
208      *
209      * @return The byte, which is being returned.
210      * @throws IllegalStateException The buffer is empty. Use {@link #hasBytes()},
211      *                               or {@link #getCurrentNumberOfBytes()}, to prevent this exception.
212      */
213     public byte read() {
214         if (currentNumberOfBytes <= 0) {
215             throw new IllegalStateException("No bytes available.");
216         }
217         final byte b = buffer[startOffset];
218         --currentNumberOfBytes;
219         if (++startOffset == buffer.length) {
220             startOffset = 0;
221         }
222         return b;
223     }
224 
225     /**
226      * Returns the given number of bytes from the buffer by storing them in
227      * the given byte array at the given offset.
228      *
229      * @param targetBuffer The byte array, where to add bytes.
230      * @param targetOffset The offset, where to store bytes in the byte array.
231      * @param length The number of bytes to return.
232      * @throws NullPointerException     The byte array {@code pBuffer} is null.
233      * @throws IllegalArgumentException Either of {@code pOffset}, or {@code length} is negative,
234      *                                  or the length of the byte array {@code targetBuffer} is too small.
235      * @throws IllegalStateException    The buffer doesn't hold the given number
236      *                                  of bytes. Use {@link #getCurrentNumberOfBytes()} to prevent this
237      *                                  exception.
238      */
239     public void read(final byte[] targetBuffer, final int targetOffset, final int length) {
240         Objects.requireNonNull(targetBuffer, "targetBuffer");
241         if (targetOffset < 0 || targetOffset >= targetBuffer.length) {
242             throw new IllegalArgumentException("Illegal offset: " + targetOffset);
243         }
244         if (length < 0 || length > buffer.length) {
245             throw new IllegalArgumentException("Illegal length: " + length);
246         }
247         if (targetOffset + length > targetBuffer.length) {
248             throw new IllegalArgumentException("The supplied byte array contains only "
249                     + targetBuffer.length + " bytes, but offset, and length would require "
250                     + (targetOffset + length - 1));
251         }
252         if (currentNumberOfBytes < length) {
253             throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes
254                     + "in the buffer, not " + length);
255         }
256         int offset = targetOffset;
257         for (int i = 0; i < length; i++) {
258             targetBuffer[offset++] = buffer[startOffset];
259             --currentNumberOfBytes;
260             if (++startOffset == buffer.length) {
261                 startOffset = 0;
262             }
263         }
264     }
265 }