    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}