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

org.tinymediamanager.scraper.util.RingBuffer Maven / Gradle / Ivy

/*
 * Copyright 2012 - 2019 Manuel Laggner
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tinymediamanager.scraper.util;

import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * This class implements a fixed-size ring buffer (aka a circular buffer) of objects. Objects are always added to the head of the buffer and removed
 * from the tail. Objects in the middle cannot be retrieved or removed. Decisions on how to handle adding an object to a full buffer are delegated to
 * an OverflowPolicy, which may have the option to fail the add, remove the last element and add, etc. The policy may do whatever it wishes with the
 * ring buffer.
 * 

* This class is fully thread-safe and reentrant. * * @param * the generic type * @author David Hooker, Manuel Laggner */ public class RingBuffer { private final int maxSize; private T[] data; private int head; private int tail; private int tailWrapCount; private boolean inOverflow = false; private AtomicInteger count = new AtomicInteger(); private AtomicInteger modCount = new AtomicInteger(); private ReentrantReadWriteLock headLock; private ReentrantReadWriteLock tailLock; /** * Instantiates a new ring buffer. * * @param maxSize * the max size */ @SuppressWarnings("unchecked") public RingBuffer(int maxSize) { headLock = new ReentrantReadWriteLock(); tailLock = new ReentrantReadWriteLock(); this.maxSize = maxSize; data = (T[]) new Object[maxSize]; head = tail = 0; } /** * Clear. */ @SuppressWarnings("unchecked") public void clear() { headLock.writeLock().lock(); tailLock.writeLock().lock(); try { head = tail = 0; count.set(0); modCount.incrementAndGet(); data = (T[]) new Object[maxSize]; } finally { tailLock.writeLock().unlock(); headLock.writeLock().unlock(); } } /** * Lock tail. */ protected void lockTail() { tailLock.writeLock().lock(); } /** * Unlock tail. */ protected void unlockTail() { tailLock.writeLock().lock(); } /** * Adds the. * * @param object * the object */ public void add(T object) { headLock.writeLock().lock(); try { if (!isEmpty()) { if (head == maxSize) { head = 0; } if (head == tail) { if (!inOverflow) { inOverflow = true; remove(); add(object); inOverflow = false; return; } else { throw new IllegalStateException("Double overflow in RingBuffer"); } } } data[head++] = object; count.incrementAndGet(); modCount.incrementAndGet(); } finally { headLock.writeLock().unlock(); } } /** * Gets the tail item. * * @return the tail item */ public T getTailItem() { return data[tail]; } /** * Removes the. * * @return the t */ public T remove() { if (isEmpty()) { return null; } tailLock.writeLock().lock(); T obj = null; try { obj = data[tail++]; if (tail == maxSize) { tail = 0; tailWrapCount++; } count.decrementAndGet(); modCount.incrementAndGet(); } finally { tailLock.writeLock().unlock(); } return obj; } /** * Removes the. * * @param numToRemove * the num to remove */ public void remove(int numToRemove) { if (isEmpty()) { return; } // don't let head move while we're removing tailLock.writeLock().lock(); headLock.readLock().lock(); try { if (tail + numToRemove >= maxSize) { int newTail = (tail + numToRemove) - maxSize; if (newTail > head) { newTail = head; } tail = newTail; tailWrapCount++; } // check for passing head else if ((tail < head) && (tail + numToRemove >= head)) { tail = head; } // recalc size if (tail == head) { count.set(0); } else { int newCount = count.get(); newCount -= numToRemove; count.set(newCount); } modCount.incrementAndGet(); } finally { headLock.readLock().unlock(); tailLock.writeLock().unlock(); } } /** * Iterator. * * @return the iterator */ public Iterator iterator() { return new RingBufferIterator<>(this); } private static class RingBufferIterator implements Iterator { private int next; private int nextWrapCount; private final RingBuffer buffer; private Mode mode; private boolean hasNext; private int expectedModCount; private enum Mode { EMPTY, MODE1, MODE2LEFT, MODE2RIGHT, START, END, INVALID } /** * Mode. * * @return the mode */ private Mode mode() { if (buffer.isEmpty()) { return Mode.EMPTY; } else if (next == buffer.tail) { return Mode.START; } else if (next == buffer.head) { return Mode.END; } else if (buffer.tail < buffer.head) { if (next < buffer.head) { return Mode.MODE1; } else { return Mode.INVALID; } } else if (next < buffer.head) { return Mode.MODE2LEFT; } else if (next > buffer.tail) { return Mode.MODE2RIGHT; } return Mode.INVALID; } /** * Instantiates a new ring buffer iterator. * * @param buffer * the buffer */ public RingBufferIterator(RingBuffer buffer) { this.buffer = buffer; next = buffer.tail; nextWrapCount = buffer.tailWrapCount; mode = Mode.START; hasNext = calcHasNext(); expectedModCount = buffer.modCount.get(); } @Override public boolean hasNext() { buffer.headLock.readLock().lock(); try { if (expectedModCount != buffer.modCount.get()) { hasNext = calcHasNext(); } } finally { buffer.headLock.readLock().unlock(); } return hasNext; } /** * Calc has next. * * @return true, if successful */ private boolean calcHasNext() { if (mode == Mode.INVALID) { return false; } Mode newMode = mode(); if ((newMode == Mode.EMPTY) || (newMode == Mode.END)) { // Done mode = newMode; return false; } if (newMode == mode) { // Not empty, not end, and no mode change, so we are still iterating // along prev path. // If the iterator wrapped 0 or 1 times then we're ok. if ((buffer.tailWrapCount == nextWrapCount) || (buffer.tailWrapCount == nextWrapCount - 1)) { // If we are just starting, assume M1 if (mode == Mode.START) { mode = Mode.MODE1; } return true; } else { // Data wrapped but iterator didn't, so next is now invalid mode = Mode.INVALID; return false; } } if (mode == Mode.END) { // We were at the end, but now we are not because newMode != END. Some // items must have been added. mode = newMode; switch (mode) { case MODE1: case MODE2LEFT: case MODE2RIGHT: case START: { return true; } default: { return false; } } } if (newMode == Mode.START) { if (buffer.tailWrapCount == nextWrapCount) { // Tail caught up, keep going mode = newMode; return true; } mode = Mode.INVALID; return false; } if (mode == Mode.MODE1) { if (newMode == Mode.MODE2RIGHT) { // M1 -> M2R if (buffer.tailWrapCount == nextWrapCount) { // only head wrapped, and we're still in the window mode = newMode; return true; } } else if (newMode == Mode.MODE2LEFT) { // M1 -> M2L if (buffer.tailWrapCount == nextWrapCount - 1) { // head wrapped AND next wrapped mode = newMode; return true; } } mode = Mode.INVALID; return false; } if (mode == Mode.MODE2LEFT) { // M2L -> M1 if (newMode == Mode.MODE1) { // tail wrapped, wrap count must be same if (buffer.tailWrapCount == nextWrapCount) { mode = newMode; return true; } } if (newMode == Mode.MODE2RIGHT) { // M2L -> M2R if (buffer.tailWrapCount == (nextWrapCount + 1)) { // tail wrapped AND head wrapped mode = newMode; return true; } } mode = Mode.INVALID; return false; } if (mode == Mode.MODE2RIGHT) { if (newMode == Mode.MODE2LEFT) { if (nextWrapCount == (buffer.tailWrapCount + 1)) { // only next wrapped, still in window mode = newMode; return true; } } if (newMode == Mode.MODE1) { if (nextWrapCount == buffer.tailWrapCount) { // if both wrapped together, then ok mode = newMode; return true; } } mode = Mode.INVALID; return false; } // This should never happen mode = Mode.INVALID; return false; } @Override public T next() { if (!hasNext) { return null; } T item = null; try { buffer.headLock.readLock().lock(); item = buffer.data[next++]; if (next == buffer.maxSize) { next = 0; nextWrapCount++; } hasNext = calcHasNext(); } finally { buffer.headLock.readLock().unlock(); } return item; } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * Count. * * @return the int */ public int count() { return count.get(); } /** * Max size. * * @return the int */ public int maxSize() { return maxSize; } /** * Checks if is empty. * * @return true, if is empty */ public boolean isEmpty() { tailLock.writeLock().lock(); int count = 0; try { count = this.count.get(); } finally { tailLock.writeLock().unlock(); } return (count == 0); } /** * Head. * * @return the int */ int head() { return head; } /** * Tail. * * @return the int */ int tail() { return tail; } /** * Tail wrap count. * * @return the int */ int tailWrapCount() { return tailWrapCount; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy