All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.commons.util.ArrayRingBuffer Maven / Gradle / Ivy

There is a newer version: 15.1.0.Dev04
Show newest version
package org.infinispan.commons.util;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.ObjLongConsumer;

/**
 * It's a growable ring buffer that allows to move tail/head sequences, clear, append, set/replace at specific positions.
 * This version requires headSequence to always be pointing at the first non null value if one is present.
 *
 * @author Francesco Nigro
 */
public final class ArrayRingBuffer {

   private static final Object[] EMPTY = new Object[0];
   // it points to the next slot after the last element
   private long tailSequence;
   // it points to the slot of the first element
   private long headSequence;
   private T[] elements;

   public ArrayRingBuffer() {
      this(0, 0);
   }

   public ArrayRingBuffer(final int initialSize) {
      this(initialSize, 0);
   }

   public ArrayRingBuffer(final int initialSize, final long headSequence) {
      if (initialSize < 0) {
         throw new IllegalArgumentException("initialSize must be greater than 0");
      }
      if (headSequence < 0) {
         throw new IllegalArgumentException("headSequence cannot be negative");
      }
      this.elements = allocate(initialSize == 0 ? 0 : findNextPositivePowerOfTwo(initialSize));
      this.headSequence = headSequence;
      this.tailSequence = headSequence;
   }

   public int size() {
      return size(true);
   }

   public int size(boolean count_null_elements) {
      if(count_null_elements)
         return (int) (tailSequence - headSequence);
      int size=0;
      final T[] els=this.elements;
      for(long i=headSequence; i < tailSequence; i++) {
         final T e = els[bufferOffset(i)];
         if(e != null)
            size++;
      }
      return size;
   }

   public long getTailSequence() {
      return tailSequence;
   }

   public long getHeadSequence() {
      return headSequence;
   }

   public void forEach(ObjLongConsumer consumer) {
      final int size = size();
      final T[] elements = this.elements;
      long sequence = headSequence;
      for (int i = 0; i < size; i++,sequence++) {
         final T e = elements[bufferOffset(sequence)];
         // sequence++;
         if (e == null) {
            continue;
         }
         consumer.accept(e, sequence);
      }
   }

   public int availableCapacityWithoutResizing() {
      return elements.length - size();
   }

   private void validateIndex(final long index) {
      validateIndex(index, true);
   }

   private void validateIndex(final long index, final boolean validateTail) {
      if (index < 0) {
         throw new IllegalArgumentException("index cannot be negative");
      }
      if (validateTail && index >= tailSequence) {
         throw new IllegalArgumentException("index cannot be greater then " + tailSequence);
      }
      if (index < headSequence) {
         throw new IllegalArgumentException("index cannot be less then " + headSequence);
      }
   }

   public boolean contains(final long index) {
      return index < tailSequence && index >= headSequence;
   }

   public void dropTailToHead() {
      dropTailTo(getHeadSequence());
   }

   public void clear() {
      dropHeadUntil(getTailSequence());
   }

   /**
    * Remove all elements from {@code tailSequence} back to {@code indexInclusive}.
* At the end of this operation {@code tailSequence} is equals to {@code indexInclusive}. * Note there can be null values between {@code tailSequence} and {@code headSequence} as only * {@code headSequence} is caught up to a non null value on modification. * *
    * eg:
    * elements = [A, B, C]
    * tail = 3
    * head = 0
    *
    * dropTailTo(1)
    *
    * elements = [A]
    * tail = 1
    * head = 0
    * 
*/ public int dropTailTo(final long indexInclusive) { if (size() == 0 || !contains(indexInclusive)) { return 0; } final int fromIncluded = bufferOffset(indexInclusive); final int toExcluded = bufferOffset(tailSequence); final T[] elements = this.elements; final int clearFrom; int removed = 0; if (toExcluded <= fromIncluded) { Arrays.fill(elements, fromIncluded, elements.length, null); removed += elements.length - fromIncluded; clearFrom = 0; } else { clearFrom = fromIncluded; } Arrays.fill(elements, clearFrom, toExcluded, null); removed += (toExcluded - clearFrom); tailSequence = indexInclusive; return removed; } /** * Remove all elements from {@code headSequence} to at least {@code indexExclusive} until a non * null value is found if present.
* At the end of this operation {@code headSequence} will be greater than or equal {@code indexExclusive}. * If there are no more values left then {@code headSequence} will equal {@code tailSequence}. * *
    * eg:
    * elements = [A, B, C]
    * tail = 3
    * head = 0
    *
    * dropHeadUntil(1)
    *
    * elements = [B, C]
    * tail = 3
    * head = 1
    * 
*/ public int dropHeadUntil(final long indexExclusive) { final long indexInclusive = indexExclusive - 1; if (size() == 0 || !contains(indexInclusive)) { return 0; } final int fromInclusive = bufferOffset(headSequence); final int toExclusive = bufferOffset(indexExclusive); final T[] elements = this.elements; final int clearFrom; int removed = 0; if (toExclusive <= fromInclusive) { Arrays.fill(elements, fromInclusive, elements.length, null); removed += (elements.length - fromInclusive); clearFrom = 0; } else { clearFrom = fromInclusive; } Arrays.fill(elements, clearFrom, toExclusive, null); removed += (toExclusive - clearFrom); // Need to also catch up to next non null value headSequence = catchUpHeadPointer(elements, indexExclusive, tailSequence); return removed; } private int bufferOffset(final long index) { return bufferOffset(index, elements.length); } private static int bufferOffset(final long index, final int elementsLength) { return (int) (index & (elementsLength - 1)); } public T get(final long index) { validateIndex(index); return elements[bufferOffset(index)]; } private T replace(final long index, final T e) { final int offset = bufferOffset(index); final T oldValue = elements[offset]; elements[offset] = e; return oldValue; } public T set(final long index, final T e) { Objects.requireNonNull(e); validateIndex(index, false); if (index < tailSequence) { return replace(index, e); } final int requiredCapacity = (int) (index - tailSequence) + 1; final int missingCapacity = requiredCapacity - availableCapacityWithoutResizing(); if (missingCapacity > 0) { growCapacity(missingCapacity); } final T[] elements = this.elements; elements[bufferOffset(index)] = e; if (index >= tailSequence) { // If we were empty, move the head along if (headSequence == tailSequence) { headSequence = index; } tailSequence = index + 1; } return null; } public void add(final T e) { Objects.requireNonNull(e); if (availableCapacityWithoutResizing() == 0) { growCapacity(1); } final long index = tailSequence; elements[bufferOffset(index)] = e; tailSequence = index + 1; } public boolean isEmpty() { return tailSequence == headSequence; } public T peek() { if (isEmpty()) { return null; } return get(headSequence); } /** Removes the element at head, does not catch up the headSequence but will increment */ public T poll() { if (isEmpty()) { return null; } final T[] elements = this.elements; long headSequence = this.headSequence; final int offset = bufferOffset(headSequence); final T e = elements[offset]; elements[offset] = null; this.headSequence = catchUpHeadPointer(elements, ++headSequence, tailSequence); return e; } /** Removes an element at index */ public T remove(long index) { if(isEmpty() || !contains(index)) return null; final T[] elements = this.elements; final int offset = bufferOffset(index); final T e = elements[offset]; elements[offset] = null; long headSequence = this.headSequence; if (index == headSequence) { // how about removal at tailSequence? tailSequence-- ? this.headSequence = catchUpHeadPointer(elements, ++headSequence, tailSequence); } return e; } private static long catchUpHeadPointer(final T[] elements, long headSequence, long tailSequence) { while (headSequence < tailSequence) { int innerOffset = bufferOffset(headSequence, elements.length); if (elements[innerOffset] != null) { break; } headSequence++; } return headSequence; } public String toString() { return String.format("[%s..%s] (%s elements)", headSequence, tailSequence, size(false)); } private void growCapacity(int delta) { assert delta > 0; final T[] oldElements = this.elements; final int newCapacity = findNextPositivePowerOfTwo(oldElements.length + delta); if (newCapacity < 0) { // see ArrayList::newCapacity throw new OutOfMemoryError(); } final T[] newElements = allocate(newCapacity); final int size = size(); final long headSequence = this.headSequence; long oldIndex = headSequence; long newIndex = headSequence; int remaining = size; while (remaining > 0) { final int fromOldIndex = bufferOffset(oldIndex, oldElements.length); final int fromNewIndex = bufferOffset(newIndex, newCapacity); final int toOldEnd = oldElements.length - fromOldIndex; final int toNewEnd = newElements.length - fromNewIndex; final int bytesToCopy = Math.min(Math.min(remaining, toOldEnd), toNewEnd); System.arraycopy(oldElements, fromOldIndex, newElements, fromNewIndex, bytesToCopy); oldIndex += bytesToCopy; newIndex += bytesToCopy; remaining -= bytesToCopy; } this.elements = newElements; } private static int findNextPositivePowerOfTwo(final int value) { return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(value - 1)); } @SuppressWarnings("unchecked") private static T[] allocate(int capacity) { return (T[]) (capacity == 0 ? EMPTY : new Object[capacity]); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy