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 * @since 2.7 31 */ 32 public class CircularByteBuffer { 33 34 private final byte[] buffer; 35 private int startOffset; 36 private int endOffset; 37 private int currentNumberOfBytes; 38 39 /** 40 * Constructs a new instance with a reasonable default buffer size ({@link IOUtils#DEFAULT_BUFFER_SIZE}). 41 */ 42 public CircularByteBuffer() { 43 this(IOUtils.DEFAULT_BUFFER_SIZE); 44 } 45 46 /** 47 * Constructs a new instance with the given buffer size. 48 * 49 * @param size the size of buffer to create 50 */ 51 public CircularByteBuffer(final int size) { 52 buffer = IOUtils.byteArray(size); 53 startOffset = 0; 54 endOffset = 0; 55 currentNumberOfBytes = 0; 56 } 57 58 /** 59 * Adds a new byte to the buffer, which will eventually be returned by following 60 * invocations of {@link #read()}. 61 * 62 * @param value The byte, which is being added to the buffer. 63 * @throws IllegalStateException The buffer is full. Use {@link #hasSpace()}, 64 * or {@link #getSpace()}, to prevent this exception. 65 */ 66 public void add(final byte value) { 67 if (currentNumberOfBytes >= buffer.length) { 68 throw new IllegalStateException("No space available"); 69 } 70 buffer[endOffset] = value; 71 ++currentNumberOfBytes; 72 if (++endOffset == buffer.length) { 73 endOffset = 0; 74 } 75 } 76 77 /** 78 * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)} 79 * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., 80 * {@code offset+length-1} of byte array {@code targetBuffer}. 81 * 82 * @param targetBuffer the buffer to copy 83 * @param offset start offset 84 * @param length length to copy 85 * @throws IllegalStateException The buffer doesn't have sufficient space. Use 86 * {@link #getSpace()} to prevent this exception. 87 * @throws IllegalArgumentException Either of {@code offset}, or {@code length} is negative. 88 * @throws NullPointerException The byte array {@code pBuffer} is null. 89 */ 90 public void add(final byte[] targetBuffer, final int offset, final int length) { 91 Objects.requireNonNull(targetBuffer, "Buffer"); 92 if (offset < 0 || offset >= targetBuffer.length) { 93 throw new IllegalArgumentException("Illegal offset: " + offset); 94 } 95 if (length < 0) { 96 throw new IllegalArgumentException("Illegal length: " + length); 97 } 98 if (currentNumberOfBytes + length > buffer.length) { 99 throw new IllegalStateException("No space available"); 100 } 101 for (int i = 0; i < length; i++) { 102 buffer[endOffset] = targetBuffer[offset + i]; 103 if (++endOffset == buffer.length) { 104 endOffset = 0; 105 } 106 } 107 currentNumberOfBytes += length; 108 } 109 110 /** 111 * Removes all bytes from the buffer. 112 */ 113 public void clear() { 114 startOffset = 0; 115 endOffset = 0; 116 currentNumberOfBytes = 0; 117 } 118 119 /** 120 * Gets the number of bytes, that are currently present in the buffer. 121 * 122 * @return the number of bytes 123 */ 124 public int getCurrentNumberOfBytes() { 125 return currentNumberOfBytes; 126 } 127 128 /** 129 * Gets the number of bytes, that can currently be added to the buffer. 130 * 131 * @return the number of bytes that can be added 132 */ 133 public int getSpace() { 134 return buffer.length - currentNumberOfBytes; 135 } 136 137 /** 138 * Tests whether the buffer is currently holding at least a single byte. 139 * 140 * @return true whether the buffer is currently holding at least a single byte. 141 */ 142 public boolean hasBytes() { 143 return currentNumberOfBytes > 0; 144 } 145 146 /** 147 * Tests whether there is currently room for a single byte in the buffer. 148 * Same as {@link #hasSpace(int) hasSpace(1)}. 149 * 150 * @return true whether there is currently room for a single byte in the buffer. 151 * @see #hasSpace(int) 152 * @see #getSpace() 153 */ 154 public boolean hasSpace() { 155 return currentNumberOfBytes < buffer.length; 156 } 157 158 /** 159 * Tests whether there is currently room for the given number of bytes in the buffer. 160 * 161 * @param count the byte count 162 * @return true whether there is currently room for the given number of bytes in the buffer. 163 * @see #hasSpace() 164 * @see #getSpace() 165 */ 166 public boolean hasSpace(final int count) { 167 return currentNumberOfBytes + count <= buffer.length; 168 } 169 170 /** 171 * Returns, whether the next bytes in the buffer are exactly those, given by 172 * {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being 173 * removed from the buffer. If the result is true, then the following invocations 174 * of {@link #read()} are guaranteed to return exactly those bytes. 175 * 176 * @param sourceBuffer the buffer to compare against 177 * @param offset start offset 178 * @param length length to compare 179 * @return True, if the next invocations of {@link #read()} will return the 180 * bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ..., 181 * {@code pOffset}+{@code length}-1 of byte array {@code pBuffer}. 182 * @throws IllegalArgumentException Either of {@code pOffset}, or {@code length} is negative. 183 * @throws NullPointerException The byte array {@code pBuffer} is null. 184 */ 185 public boolean peek(final byte[] sourceBuffer, final int offset, final int length) { 186 Objects.requireNonNull(sourceBuffer, "Buffer"); 187 if (offset < 0 || offset >= sourceBuffer.length) { 188 throw new IllegalArgumentException("Illegal offset: " + offset); 189 } 190 if (length < 0 || length > buffer.length) { 191 throw new IllegalArgumentException("Illegal length: " + length); 192 } 193 if (length < currentNumberOfBytes) { 194 return false; 195 } 196 int localOffset = startOffset; 197 for (int i = 0; i < length; i++) { 198 if (buffer[localOffset] != sourceBuffer[i + offset]) { 199 return false; 200 } 201 if (++localOffset == buffer.length) { 202 localOffset = 0; 203 } 204 } 205 return true; 206 } 207 208 /** 209 * Returns the next byte from the buffer, removing it at the same time, so 210 * that following invocations won't return it again. 211 * 212 * @return The byte, which is being returned. 213 * @throws IllegalStateException The buffer is empty. Use {@link #hasBytes()}, 214 * or {@link #getCurrentNumberOfBytes()}, to prevent this exception. 215 */ 216 public byte read() { 217 if (currentNumberOfBytes <= 0) { 218 throw new IllegalStateException("No bytes available."); 219 } 220 final byte b = buffer[startOffset]; 221 --currentNumberOfBytes; 222 if (++startOffset == buffer.length) { 223 startOffset = 0; 224 } 225 return b; 226 } 227 228 /** 229 * Returns the given number of bytes from the buffer by storing them in 230 * the given byte array at the given offset. 231 * 232 * @param targetBuffer The byte array, where to add bytes. 233 * @param targetOffset The offset, where to store bytes in the byte array. 234 * @param length The number of bytes to return. 235 * @throws NullPointerException The byte array {@code pBuffer} is null. 236 * @throws IllegalArgumentException Either of {@code pOffset}, or {@code length} is negative, 237 * or the length of the byte array {@code targetBuffer} is too small. 238 * @throws IllegalStateException The buffer doesn't hold the given number 239 * of bytes. Use {@link #getCurrentNumberOfBytes()} to prevent this 240 * exception. 241 */ 242 public void read(final byte[] targetBuffer, final int targetOffset, final int length) { 243 Objects.requireNonNull(targetBuffer, "targetBuffer"); 244 if (targetOffset < 0 || targetOffset >= targetBuffer.length) { 245 throw new IllegalArgumentException("Illegal offset: " + targetOffset); 246 } 247 if (length < 0 || length > buffer.length) { 248 throw new IllegalArgumentException("Illegal length: " + length); 249 } 250 if (targetOffset + length > targetBuffer.length) { 251 throw new IllegalArgumentException("The supplied byte array contains only " 252 + targetBuffer.length + " bytes, but offset, and length would require " 253 + (targetOffset + length - 1)); 254 } 255 if (currentNumberOfBytes < length) { 256 throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes 257 + "in the buffer, not " + length); 258 } 259 int offset = targetOffset; 260 for (int i = 0; i < length; i++) { 261 targetBuffer[offset++] = buffer[startOffset]; 262 --currentNumberOfBytes; 263 if (++startOffset == buffer.length) { 264 startOffset = 0; 265 } 266 } 267 } 268 }