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 */ 030public class CircularByteBuffer { 031 private final byte[] buffer; 032 private int startOffset; 033 private int endOffset; 034 private int currentNumberOfBytes; 035 036 /** 037 * Constructs a new instance with a reasonable default buffer size ({@link IOUtils#DEFAULT_BUFFER_SIZE}). 038 */ 039 public CircularByteBuffer() { 040 this(IOUtils.DEFAULT_BUFFER_SIZE); 041 } 042 043 /** 044 * Constructs a new instance with the given buffer size. 045 * 046 * @param size the size of buffer to create 047 */ 048 public CircularByteBuffer(final int size) { 049 buffer = IOUtils.byteArray(size); 050 startOffset = 0; 051 endOffset = 0; 052 currentNumberOfBytes = 0; 053 } 054 055 /** 056 * Adds a new byte to the buffer, which will eventually be returned by following 057 * invocations of {@link #read()}. 058 * 059 * @param value The byte, which is being added to the buffer. 060 * @throws IllegalStateException The buffer is full. Use {@link #hasSpace()}, 061 * or {@link #getSpace()}, to prevent this exception. 062 */ 063 public void add(final byte value) { 064 if (currentNumberOfBytes >= buffer.length) { 065 throw new IllegalStateException("No space available"); 066 } 067 buffer[endOffset] = value; 068 ++currentNumberOfBytes; 069 if (++endOffset == buffer.length) { 070 endOffset = 0; 071 } 072 } 073 074 /** 075 * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)} 076 * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., 077 * {@code offset+length-1} of byte array {@code targetBuffer}. 078 * 079 * @param targetBuffer the buffer to copy 080 * @param offset start offset 081 * @param length length to copy 082 * @throws IllegalStateException The buffer doesn't have sufficient space. Use 083 * {@link #getSpace()} to prevent this exception. 084 * @throws IllegalArgumentException Either of {@code offset}, or {@code length} is negative. 085 * @throws NullPointerException The byte array {@code pBuffer} is null. 086 */ 087 public void add(final byte[] targetBuffer, final int offset, final int length) { 088 Objects.requireNonNull(targetBuffer, "Buffer"); 089 if (offset < 0 || offset >= targetBuffer.length) { 090 throw new IllegalArgumentException("Illegal offset: " + offset); 091 } 092 if (length < 0) { 093 throw new IllegalArgumentException("Illegal length: " + length); 094 } 095 if (currentNumberOfBytes + length > buffer.length) { 096 throw new IllegalStateException("No space available"); 097 } 098 for (int i = 0; i < length; i++) { 099 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}