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 * Creates a new instance with the given buffer size. 038 * 039 * @param pSize the size of buffer to create 040 */ 041 public CircularByteBuffer(final int pSize) { 042 buffer = new byte[pSize]; 043 startOffset = 0; 044 endOffset = 0; 045 currentNumberOfBytes = 0; 046 } 047 048 /** 049 * Creates a new instance with a reasonable default buffer size ({@link IOUtils#DEFAULT_BUFFER_SIZE}). 050 */ 051 public CircularByteBuffer() { 052 this(IOUtils.DEFAULT_BUFFER_SIZE); 053 } 054 055 /** 056 * Returns the next byte from the buffer, removing it at the same time, so 057 * that following invocations won't return it again. 058 * 059 * @return The byte, which is being returned. 060 * @throws IllegalStateException The buffer is empty. Use {@link #hasBytes()}, 061 * or {@link #getCurrentNumberOfBytes()}, to prevent this exception. 062 */ 063 public byte read() { 064 if (currentNumberOfBytes <= 0) { 065 throw new IllegalStateException("No bytes available."); 066 } 067 final byte b = buffer[startOffset]; 068 --currentNumberOfBytes; 069 if (++startOffset == buffer.length) { 070 startOffset = 0; 071 } 072 return b; 073 } 074 075 /** 076 * Returns the given number of bytes from the buffer by storing them in 077 * the given byte array at the given offset. 078 * 079 * @param targetBuffer The byte array, where to add bytes. 080 * @param targetOffset The offset, where to store bytes in the byte array. 081 * @param length The number of bytes to return. 082 * @throws NullPointerException The byte array {@code pBuffer} is null. 083 * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative, 084 * or the length of the byte array {@code pBuffer} is too small. 085 * @throws IllegalStateException The buffer doesn't hold the given number 086 * of bytes. Use {@link #getCurrentNumberOfBytes()} to prevent this 087 * exception. 088 */ 089 public void read(final byte[] targetBuffer, final int targetOffset, final int length) { 090 Objects.requireNonNull(targetBuffer); 091 if (targetOffset < 0 || targetOffset >= targetBuffer.length) { 092 throw new IllegalArgumentException("Invalid offset: " + targetOffset); 093 } 094 if (length < 0 || length > buffer.length) { 095 throw new IllegalArgumentException("Invalid length: " + length); 096 } 097 if (targetOffset + length > targetBuffer.length) { 098 throw new IllegalArgumentException("The supplied byte array contains only " 099 + targetBuffer.length + " bytes, but offset, and length would require " 100 + (targetOffset + length - 1)); 101 } 102 if (currentNumberOfBytes < length) { 103 throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes 104 + "in the buffer, not " + length); 105 } 106 int offset = targetOffset; 107 for (int i = 0; i < length; i++) { 108 targetBuffer[offset++] = buffer[startOffset]; 109 --currentNumberOfBytes; 110 if (++startOffset == buffer.length) { 111 startOffset = 0; 112 } 113 } 114 } 115 116 /** 117 * Adds a new byte to the buffer, which will eventually be returned by following 118 * invocations of {@link #read()}. 119 * 120 * @param value The byte, which is being added to the buffer. 121 * @throws IllegalStateException The buffer is full. Use {@link #hasSpace()}, 122 * or {@link #getSpace()}, to prevent this exception. 123 */ 124 public void add(final byte value) { 125 if (currentNumberOfBytes >= buffer.length) { 126 throw new IllegalStateException("No space available"); 127 } 128 buffer[endOffset] = value; 129 ++currentNumberOfBytes; 130 if (++endOffset == buffer.length) { 131 endOffset = 0; 132 } 133 } 134 135 /** 136 * Returns, whether the next bytes in the buffer are exactly those, given by 137 * {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being 138 * removed from the buffer. If the result is true, then the following invocations 139 * of {@link #read()} are guaranteed to return exactly those bytes. 140 * 141 * @param sourceBuffer the buffer to compare against 142 * @param offset start offset 143 * @param length length to compare 144 * @return True, if the next invocations of {@link #read()} will return the 145 * bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ..., 146 * {@code pOffset}+{@code pLength}-1 of byte array {@code pBuffer}. 147 * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative. 148 * @throws NullPointerException The byte array {@code pBuffer} is null. 149 */ 150 public boolean peek(final byte[] sourceBuffer, final int offset, final int length) { 151 Objects.requireNonNull(sourceBuffer, "Buffer"); 152 if (offset < 0 || offset >= sourceBuffer.length) { 153 throw new IllegalArgumentException("Invalid offset: " + offset); 154 } 155 if (length < 0 || length > buffer.length) { 156 throw new IllegalArgumentException("Invalid length: " + length); 157 } 158 if (length < currentNumberOfBytes) { 159 return false; 160 } 161 int localOffset = startOffset; 162 for (int i = 0; i < length; i++) { 163 if (buffer[localOffset] != sourceBuffer[i + offset]) { 164 return false; 165 } 166 if (++localOffset == buffer.length) { 167 localOffset = 0; 168 } 169 } 170 return true; 171 } 172 173 /** 174 * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)} 175 * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., 176 * {@code offset+length-1} of byte array {@code targetBuffer}. 177 * 178 * @param targetBuffer the buffer to copy 179 * @param offset start offset 180 * @param length length to copy 181 * @throws IllegalStateException The buffer doesn't have sufficient space. Use 182 * {@link #getSpace()} to prevent this exception. 183 * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative. 184 * @throws NullPointerException The byte array {@code pBuffer} is null. 185 */ 186 public void add(final byte[] targetBuffer, final int offset, final int length) { 187 Objects.requireNonNull(targetBuffer, "Buffer"); 188 if (offset < 0 || offset >= targetBuffer.length) { 189 throw new IllegalArgumentException("Invalid offset: " + offset); 190 } 191 if (length < 0) { 192 throw new IllegalArgumentException("Invalid length: " + length); 193 } 194 if (currentNumberOfBytes + length > buffer.length) { 195 throw new IllegalStateException("No space available"); 196 } 197 for (int i = 0; i < length; i++) { 198 buffer[endOffset] = targetBuffer[offset + i]; 199 if (++endOffset == buffer.length) { 200 endOffset = 0; 201 } 202 } 203 currentNumberOfBytes += length; 204 } 205 206 /** 207 * Returns, whether there is currently room for a single byte in the buffer. 208 * Same as {@link #hasSpace(int) hasSpace(1)}. 209 * 210 * @return true if there is space for a byte 211 * @see #hasSpace(int) 212 * @see #getSpace() 213 */ 214 public boolean hasSpace() { 215 return currentNumberOfBytes < buffer.length; 216 } 217 218 /** 219 * Returns, whether there is currently room for the given number of bytes in the buffer. 220 * 221 * @param count the byte count 222 * @return true if there is space for the given number of bytes 223 * @see #hasSpace() 224 * @see #getSpace() 225 */ 226 public boolean hasSpace(final int count) { 227 return currentNumberOfBytes + count <= buffer.length; 228 } 229 230 /** 231 * Returns, whether the buffer is currently holding, at least, a single byte. 232 * 233 * @return true if the buffer is not empty 234 */ 235 public boolean hasBytes() { 236 return currentNumberOfBytes > 0; 237 } 238 239 /** 240 * Returns the number of bytes, that can currently be added to the buffer. 241 * 242 * @return the number of bytes that can be added 243 */ 244 public int getSpace() { 245 return buffer.length - currentNumberOfBytes; 246 } 247 248 /** 249 * Returns the number of bytes, that are currently present in the buffer. 250 * 251 * @return the number of bytes 252 */ 253 public int getCurrentNumberOfBytes() { 254 return currentNumberOfBytes; 255 } 256 257 /** 258 * Removes all bytes from the buffer. 259 */ 260 public void clear() { 261 startOffset = 0; 262 endOffset = 0; 263 currentNumberOfBytes = 0; 264 } 265}