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