CircularByteBuffer.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.io.input.buffer;

  18. import java.util.Objects;

  19. import org.apache.commons.io.IOUtils;

  20. /**
  21.  * A buffer, which doesn't need reallocation of byte arrays, because it
  22.  * reuses a single byte array. This works particularly well, if reading
  23.  * from the buffer takes place at the same time than writing to. Such is the
  24.  * case, for example, when using the buffer within a filtering input stream,
  25.  * like the {@link CircularBufferInputStream}.
  26.  *
  27.  * @since 2.7
  28.  */
  29. public class CircularByteBuffer {

  30.     private final byte[] buffer;
  31.     private int startOffset;
  32.     private int endOffset;
  33.     private int currentNumberOfBytes;

  34.     /**
  35.      * Constructs a new instance with a reasonable default buffer size ({@link IOUtils#DEFAULT_BUFFER_SIZE}).
  36.      */
  37.     public CircularByteBuffer() {
  38.         this(IOUtils.DEFAULT_BUFFER_SIZE);
  39.     }

  40.     /**
  41.      * Constructs a new instance with the given buffer size.
  42.      *
  43.      * @param size the size of buffer to create
  44.      */
  45.     public CircularByteBuffer(final int size) {
  46.         buffer = IOUtils.byteArray(size);
  47.         startOffset = 0;
  48.         endOffset = 0;
  49.         currentNumberOfBytes = 0;
  50.     }

  51.     /**
  52.      * Adds a new byte to the buffer, which will eventually be returned by following
  53.      * invocations of {@link #read()}.
  54.      *
  55.      * @param value The byte, which is being added to the buffer.
  56.      * @throws IllegalStateException The buffer is full. Use {@link #hasSpace()},
  57.      *                               or {@link #getSpace()}, to prevent this exception.
  58.      */
  59.     public void add(final byte value) {
  60.         if (currentNumberOfBytes >= buffer.length) {
  61.             throw new IllegalStateException("No space available");
  62.         }
  63.         buffer[endOffset] = value;
  64.         ++currentNumberOfBytes;
  65.         if (++endOffset == buffer.length) {
  66.             endOffset = 0;
  67.         }
  68.     }

  69.     /**
  70.      * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)}
  71.      * for the bytes at offsets {@code offset+0}, {@code offset+1}, ...,
  72.      * {@code offset+length-1} of byte array {@code targetBuffer}.
  73.      *
  74.      * @param targetBuffer the buffer to copy
  75.      * @param offset start offset
  76.      * @param length length to copy
  77.      * @throws IllegalStateException    The buffer doesn't have sufficient space. Use
  78.      *                                  {@link #getSpace()} to prevent this exception.
  79.      * @throws IllegalArgumentException Either of {@code offset}, or {@code length} is negative.
  80.      * @throws NullPointerException     The byte array {@code pBuffer} is null.
  81.      */
  82.     public void add(final byte[] targetBuffer, final int offset, final int length) {
  83.         Objects.requireNonNull(targetBuffer, "Buffer");
  84.         if (offset < 0 || offset >= targetBuffer.length) {
  85.             throw new IllegalArgumentException("Illegal offset: " + offset);
  86.         }
  87.         if (length < 0) {
  88.             throw new IllegalArgumentException("Illegal length: " + length);
  89.         }
  90.         if (currentNumberOfBytes + length > buffer.length) {
  91.             throw new IllegalStateException("No space available");
  92.         }
  93.         for (int i = 0; i < length; i++) {
  94.             buffer[endOffset] = targetBuffer[offset + i];
  95.             if (++endOffset == buffer.length) {
  96.                 endOffset = 0;
  97.             }
  98.         }
  99.         currentNumberOfBytes += length;
  100.     }

  101.     /**
  102.      * Removes all bytes from the buffer.
  103.      */
  104.     public void clear() {
  105.         startOffset = 0;
  106.         endOffset = 0;
  107.         currentNumberOfBytes = 0;
  108.     }

  109.     /**
  110.      * Gets the number of bytes, that are currently present in the buffer.
  111.      *
  112.      * @return the number of bytes
  113.      */
  114.     public int getCurrentNumberOfBytes() {
  115.         return currentNumberOfBytes;
  116.     }

  117.     /**
  118.      * Gets the number of bytes, that can currently be added to the buffer.
  119.      *
  120.      * @return the number of bytes that can be added
  121.      */
  122.     public int getSpace() {
  123.         return buffer.length - currentNumberOfBytes;
  124.     }

  125.     /**
  126.      * Tests whether the buffer is currently holding at least a single byte.
  127.      *
  128.      * @return true whether the buffer is currently holding at least a single byte.
  129.      */
  130.     public boolean hasBytes() {
  131.         return currentNumberOfBytes > 0;
  132.     }

  133.     /**
  134.      * Tests whether there is currently room for a single byte in the buffer.
  135.      * Same as {@link #hasSpace(int) hasSpace(1)}.
  136.      *
  137.      * @return true whether there is currently room for a single byte in the buffer.
  138.      * @see #hasSpace(int)
  139.      * @see #getSpace()
  140.      */
  141.     public boolean hasSpace() {
  142.         return currentNumberOfBytes < buffer.length;
  143.     }

  144.     /**
  145.      * Tests whether there is currently room for the given number of bytes in the buffer.
  146.      *
  147.      * @param count the byte count
  148.      * @return true whether there is currently room for the given number of bytes in the buffer.
  149.      * @see #hasSpace()
  150.      * @see #getSpace()
  151.      */
  152.     public boolean hasSpace(final int count) {
  153.         return currentNumberOfBytes + count <= buffer.length;
  154.     }

  155.     /**
  156.      * Returns, whether the next bytes in the buffer are exactly those, given by
  157.      * {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being
  158.      * removed from the buffer. If the result is true, then the following invocations
  159.      * of {@link #read()} are guaranteed to return exactly those bytes.
  160.      *
  161.      * @param sourceBuffer the buffer to compare against
  162.      * @param offset start offset
  163.      * @param length length to compare
  164.      * @return True, if the next invocations of {@link #read()} will return the
  165.      * bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ...,
  166.      * {@code pOffset}+{@code length}-1 of byte array {@code pBuffer}.
  167.      * @throws IllegalArgumentException Either of {@code pOffset}, or {@code length} is negative.
  168.      * @throws NullPointerException     The byte array {@code pBuffer} is null.
  169.      */
  170.     public boolean peek(final byte[] sourceBuffer, final int offset, final int length) {
  171.         Objects.requireNonNull(sourceBuffer, "Buffer");
  172.         if (offset < 0 || offset >= sourceBuffer.length) {
  173.             throw new IllegalArgumentException("Illegal offset: " + offset);
  174.         }
  175.         if (length < 0 || length > buffer.length) {
  176.             throw new IllegalArgumentException("Illegal length: " + length);
  177.         }
  178.         if (length < currentNumberOfBytes) {
  179.             return false;
  180.         }
  181.         int localOffset = startOffset;
  182.         for (int i = 0; i < length; i++) {
  183.             if (buffer[localOffset] != sourceBuffer[i + offset]) {
  184.                 return false;
  185.             }
  186.             if (++localOffset == buffer.length) {
  187.                 localOffset = 0;
  188.             }
  189.         }
  190.         return true;
  191.     }

  192.     /**
  193.      * Returns the next byte from the buffer, removing it at the same time, so
  194.      * that following invocations won't return it again.
  195.      *
  196.      * @return The byte, which is being returned.
  197.      * @throws IllegalStateException The buffer is empty. Use {@link #hasBytes()},
  198.      *                               or {@link #getCurrentNumberOfBytes()}, to prevent this exception.
  199.      */
  200.     public byte read() {
  201.         if (currentNumberOfBytes <= 0) {
  202.             throw new IllegalStateException("No bytes available.");
  203.         }
  204.         final byte b = buffer[startOffset];
  205.         --currentNumberOfBytes;
  206.         if (++startOffset == buffer.length) {
  207.             startOffset = 0;
  208.         }
  209.         return b;
  210.     }

  211.     /**
  212.      * Returns the given number of bytes from the buffer by storing them in
  213.      * the given byte array at the given offset.
  214.      *
  215.      * @param targetBuffer The byte array, where to add bytes.
  216.      * @param targetOffset The offset, where to store bytes in the byte array.
  217.      * @param length The number of bytes to return.
  218.      * @throws NullPointerException     The byte array {@code pBuffer} is null.
  219.      * @throws IllegalArgumentException Either of {@code pOffset}, or {@code length} is negative,
  220.      *                                  or the length of the byte array {@code targetBuffer} is too small.
  221.      * @throws IllegalStateException    The buffer doesn't hold the given number
  222.      *                                  of bytes. Use {@link #getCurrentNumberOfBytes()} to prevent this
  223.      *                                  exception.
  224.      */
  225.     public void read(final byte[] targetBuffer, final int targetOffset, final int length) {
  226.         Objects.requireNonNull(targetBuffer, "targetBuffer");
  227.         if (targetOffset < 0 || targetOffset >= targetBuffer.length) {
  228.             throw new IllegalArgumentException("Illegal offset: " + targetOffset);
  229.         }
  230.         if (length < 0 || length > buffer.length) {
  231.             throw new IllegalArgumentException("Illegal length: " + length);
  232.         }
  233.         if (targetOffset + length > targetBuffer.length) {
  234.             throw new IllegalArgumentException("The supplied byte array contains only "
  235.                     + targetBuffer.length + " bytes, but offset, and length would require "
  236.                     + (targetOffset + length - 1));
  237.         }
  238.         if (currentNumberOfBytes < length) {
  239.             throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes
  240.                     + "in the buffer, not " + length);
  241.         }
  242.         int offset = targetOffset;
  243.         for (int i = 0; i < length; i++) {
  244.             targetBuffer[offset++] = buffer[startOffset];
  245.             --currentNumberOfBytes;
  246.             if (++startOffset == buffer.length) {
  247.                 startOffset = 0;
  248.             }
  249.         }
  250.     }
  251. }