001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io.input.buffer; 018 019import java.util.Objects; 020 021import org.apache.commons.io.IOUtils; 022 023/** 024 * A buffer, which doesn't need reallocation of byte arrays, because it 025 * reuses a single byte array. This works particularly well, if reading 026 * from the buffer takes place at the same time than writing to. Such is the 027 * case, for example, when using the buffer within a filtering input stream, 028 * like the {@link CircularBufferInputStream}. 029 * 030 * @since 2.7 031 */ 032public class CircularByteBuffer { 033 034 private final byte[] buffer; 035 private int startOffset; 036 private int endOffset; 037 private int currentNumberOfBytes; 038 039 /** 040 * Constructs a new instance with a reasonable default buffer size ({@link IOUtils#DEFAULT_BUFFER_SIZE}). 041 */ 042 public CircularByteBuffer() { 043 this(IOUtils.DEFAULT_BUFFER_SIZE); 044 } 045 046 /** 047 * Constructs a new instance with the given buffer size. 048 * 049 * @param size the size of buffer to create 050 */ 051 public CircularByteBuffer(final int size) { 052 buffer = IOUtils.byteArray(size); 053 startOffset = 0; 054 endOffset = 0; 055 currentNumberOfBytes = 0; 056 } 057 058 /** 059 * Adds a new byte to the buffer, which will eventually be returned by following 060 * invocations of {@link #read()}. 061 * 062 * @param value The byte, which is being added to the buffer. 063 * @throws IllegalStateException The buffer is full. Use {@link #hasSpace()}, 064 * or {@link #getSpace()}, to prevent this exception. 065 */ 066 public void add(final byte value) { 067 if (currentNumberOfBytes >= buffer.length) { 068 throw new IllegalStateException("No space available"); 069 } 070 buffer[endOffset] = value; 071 ++currentNumberOfBytes; 072 if (++endOffset == buffer.length) { 073 endOffset = 0; 074 } 075 } 076 077 /** 078 * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)} 079 * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., 080 * {@code offset+length-1} of byte array {@code targetBuffer}. 081 * 082 * @param targetBuffer the buffer to copy 083 * @param offset start offset 084 * @param length length to copy 085 * @throws IllegalStateException The buffer doesn't have sufficient space. Use 086 * {@link #getSpace()} to prevent this exception. 087 * @throws IllegalArgumentException Either of {@code offset}, or {@code length} is negative. 088 * @throws NullPointerException The byte array {@code pBuffer} is null. 089 */ 090 public void add(final byte[] targetBuffer, final int offset, final int length) { 091 Objects.requireNonNull(targetBuffer, "Buffer"); 092 if (offset < 0 || offset >= targetBuffer.length) { 093 throw new IllegalArgumentException("Illegal offset: " + offset); 094 } 095 if (length < 0) { 096 throw new IllegalArgumentException("Illegal length: " + length); 097 } 098 if (currentNumberOfBytes + length > buffer.length) { 099 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}