/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Nov 8, 2006 */ package com.bigdata.cache; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; /** * <p> * A unsynchronized fixed capacity ring buffer. The buffer does not accept * <code>null</code> elements. If you want a thread-safe {@link Queue} * consider {@link ArrayBlockingQueue} instead. * </p> * <p> * Note: The "head" of the ring buffer is the insertion point, NOT the MRU * element which is located at the previous "head" index. The "tail" of the ring * buffer is the LRU position. Unfortunately, these labels are exactly the * reverse of the labels used to describe the {@link Queue} semantics. * </p> * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ * * @param <T> * The reference type stored in the buffer. */ public class RingBuffer<T> implements Queue<T> { /** * The capacity of the buffer. */ protected final int capacity; /** * The internal fixed capacity buffer. * <p> * References are inserted at the {@link #head}, which is then * post-incremented using <code>(head + 1) % capacity</code>. So * {@link #head} is always the next position at which a reference would be * inserted. See {@link #offer(Object)} * <p> * References are removed from the {@link #tail}, which is then * post-incremented using <code>(tail + 1) % capacity</code>. So * {@link #tail} is always the next element to take from the buffer IFF the * buffer is non-empty. See {@link #poll()} * <p> * The buffer is empty IFF {@link #size()} EQ ZERO (0). * <p> * The buffer is full IFF {@link #size()} EQ {@link #capacity()}. * <p> * {@link #head} EQ {@link #tail} implies that the buffer is either full or * empty. That condition is preserved by {@link #remove()}, which must * adjust the {@link #head} (the insertion point) so that it does not * "loose" capacity when elements are removed from the buffer by random * access. */ private final T[] refs; /** * The head (the insertion point for the next reference). */ private int head = 0; /** * The tail (the LRU position and the eviction point). */ private int tail = 0; /** * The #of elements in the buffer. The buffer is empty when this field is * zero. The buffer is full when this field equals the {@link #capacity}. * <p> * Note: Exposed to {@link HardReferenceQueue} as an optimization for * {@link #isFull()}. */ protected int size = 0; /** * Ctor. * * @param capacity * The capacity of the buffer. */ @SuppressWarnings("unchecked") public RingBuffer(final int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; this.refs = (T[]) new Object[capacity]; } /** * The capacity of the buffer as specified to the constructor. */ final public int capacity() { return capacity; } final public int size() { return size; } final public boolean isEmpty() { return size == 0; } /** * True iff the buffer is full. */ final public boolean isFull() { return size == capacity; } public boolean add(final T ref) throws IllegalStateException { if (ref == null) throw new NullPointerException(); beforeOffer( ref ); if (size == capacity/* isFull() inlined */) throw new IllegalStateException(); refs[head] = ref; head = (head + 1) % capacity; size++; return true; // if (!offer(ref)) // throw new IllegalStateException(); // // return true; } public boolean offer(final T ref) { if (ref == null) throw new NullPointerException(); beforeOffer( ref ); if (size == capacity/* isFull() inlined */) return false; refs[head] = ref; head = (head + 1) % capacity; size++; return true; } /** * All attempts to add an element to the buffer invoke this hook before * checking the remaining capacity in the buffer. * <p> * This hook provides an opportunity to realize an eviction protocol and is * used for that purpose by {@link HardReferenceQueue}. It is also used to * realize the the stale reference protocol in * {@link SynchronizedHardReferenceQueueWithTimeout}. * * @todo it can be used to realize the chunk combiner on add protocol as * well. */ protected void beforeOffer(final T ref) { // NOP } public boolean addAll(final Collection<? extends T> c) { boolean modified = false; for (T e : c) { add(e); modified = true; } return modified; } /** * Clears the buffer, setting the references in the buffer to * <code>null</code>. */ public void clear() { clear(true/* clearRefs */); } /** * Clears the buffer (sets the index of the head and the tail to zero and * sets the count to zero). * * @param clearRefs * When <code>true</code> the references are explicitly set to * <code>null</code> which can facilitate garbage collection. */ final public void clear(final boolean clearRefs) { if( clearRefs ) { /* * Evict all references, clearing each as we go. */ while( size > 0 ) { refs[tail] = null; // drop LRU reference. size--; // update #of references. tail = (tail + 1) % capacity; // update tail. } } // reset to initial conditions. head = tail = size = 0; } /* * package private methods used to write the unit tests. */ /** * The head index (the MRU position / insertion point). */ final int getHeadIndex() { return head; } /** * The tail index (the LRU position / eviction point). */ final int getTailIndex() { return tail; } /** * Return the buffer elements in MRU (head) to LRU (tail) order. */ public Object[] toArray() { return toArray( new Object[size] ); } /** * Return the buffer elements in MRU (head) to LRU (tail) order. */ @SuppressWarnings("unchecked") public <TX> TX[] toArray(final TX[] a) { final TX[] r = a.length >= size ? a : (TX[]) java.lang.reflect.Array .newInstance(a.getClass().getComponentType(), size); int n = 0; for (int i = tail; n < size; n++) { final T ref = refs[ i ]; assert ref != null; r[n] = (TX) ref; i = (i + 1) % capacity; // update index. } if (n < r.length) { // flag end of array w/ null iff over capacity @todo unit test. r[n] = null; } return r; } /** * Return the n<i>th</i> element in the buffer. The index positions are * counted from the MRU (the insertion point), which has an index of ZERO * (0), to the LRU position (the eviction point), which as an index of * {@link #size()}-1. * * @param index * The index of the desired element. * * @return The element at that index. * * @throws IllegalArgumentException * if the index is less than ZERO (0). * @throws IllegalArgumentException * if the index is greater than or equal to the {@link #size()}. */ final public T get(final int index) { if (index < 0 || index >= size) throw new IllegalArgumentException(); // the effective index. final int i = (tail + index) % capacity; // the element at that index. return refs[ i ]; } /** * Return the reference at the specified index in the backing array. * <p> * Note: Unlike {@link #get(int)}, this method does not adjust by the index * of the <code>tail</code>. It is intended for use by subclasses which * require low level access into the backing array. * * @param i * The index into the backing array. * * @return The reference at that index. * * @see <a * href="https://sourceforge.net/apps/trac/bigdata/ticket/465#comment:2"> * Too many GRS reads</a> */ final protected T _get(final int i) { return refs[i]; } /** * Remove the element at the specified index in the buffer. The index * positions are counted from the MRU (the insertion point), which has an * index of ZERO (0), to the LRU position (the eviction point), which as an * index of {@link #size()}-1. * * @param index * The index of the element to be removed. * * @return The element at that index. * * @throws IllegalArgumentException * if the index is less than ZERO (0). * @throws IllegalArgumentException * if the index is greater than or equal to the {@link #size()}. */ T remove(final int index) { if (index < 0 || index >= size) throw new IllegalArgumentException(); // if (index + 1 == size) { // // // remove the LRU position. // return remove(); // // } /* * Otherwise we are removing some non-LRU element. */ // the effective index. int i = (tail + index) % capacity; // the element at that index. final T ref = refs[i]; // clear reference from the buffer. refs[i] = null; for (;;) { final int nexti = (i + 1) % capacity; // update index. if (nexti != head) { refs[i] = refs[nexti]; i = nexti; } else { refs[i] = null; head = i; break; } } // there is one less element in the buffer. size--; return ref; } final public T element() throws NoSuchElementException { if (size == 0) throw new NoSuchElementException(); return peek(); } /** * Note: This is the reference at the "tail" of the {@link RingBuffer} (the * LRU position). */ final public T peek() { if (size == 0) { // buffer is empty. return null; } return refs[tail]; } final public T poll() { // The buffer must not be empty. if (size <= 0) return null; final T ref = refs[tail]; // LRU reference. refs[tail] = null; // drop reference. size--; // update #of references. tail = (tail + 1) % capacity; // update tail. return ref; } /** * Scan the last nscan references for this reference. If found, return * immediately. * * @param nscan * The #of positions to scan, starting with the most recently * added reference. * @param ref * The reference for which we are scanning. * * @return True iff we found <i>ref</i> in the scanned queue positions. */ public boolean scanHead(final int nscan, final T ref) { if (nscan <= 0) throw new IllegalArgumentException(); if (ref == null) throw new IllegalArgumentException(); /* * Note: This loop goes backwards from the head. Since the head is the * insertion point, we decrement the head position before testing the * reference. If the head is zero, then we wrap around. This carries * the head back to the last index in the array (capacity-1). * * Note: This uses local variables to shadow the instance variables * so that we do not modify the state of the cache as a side effect. */ int head = this.head; int count = this.size; for (int i = 0; i < nscan && count > 0; i++) { head = (head == 0 ? capacity - 1 : head - 1); // update head. count--; // update #of references. if (refs[head] == ref) { // Found a match. return true; } } return false; } /** * Return true iff the reference is found in the first N positions scanning * backwards from the tail of the queue. * * @param nscan * The #of positions to be scanned. When one (1) only the tail of * the queue is scanned. * @param ref * The reference to scan for. * * @return True iff the reference is found in the last N queue positions * counting backwards from the tail. * * @todo Write unit tests for this method. */ final public boolean scanTail(final int nscan, final T ref) { if (nscan <= 0) throw new IllegalArgumentException(); if (ref == null) throw new IllegalArgumentException(); for (int n = 0, i = tail; n < nscan; n++) { if (ref == refs[i]) return true; i = (i + 1) % capacity; // update index. } return false; } public T remove() throws NoSuchElementException { final T ref = poll(); if (ref == null) throw new NoSuchElementException(); return ref; } public boolean contains(final Object ref) { if (ref == null) throw new NullPointerException(); // MRU to LRU scan. for (int n = 0, i = tail; n < size; n++) { if (ref == refs[i]) return true; i = (i + 1) % capacity; // update index. } return false; } public boolean containsAll(final Collection<?> c) { if (c == null) throw new NullPointerException(); if (c == this) return true; // throw new IllegalArgumentException(); for( Object e : c ) { if(!contains(e)) { // something is missing. return false; } } return true; } /** * Return an iterator over the buffer visiting elements in LRU to MRU order * (the order in which those elements would be read by {@link #poll()}). The * iterator supports {@link Iterator#remove()}. The iterator is NOT * thread-safe. */ public Iterator<T> iterator() { return new MyIterator(); } /** * Iterator (not thread-safe). * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan * Thompson</a> * @version $Id$ */ private class MyIterator implements Iterator<T> { private int next = 0; private int current = -1; private MyIterator() { } public boolean hasNext() { return next < size; } public T next() { if (!hasNext()) throw new NoSuchElementException(); final T ref = get(next); current = next; next++; return ref; } public void remove() { final int i = current; if (current == -1) throw new IllegalStateException(); current = -1; final int tmp = tail; RingBuffer.this.remove(i); // re-position index into buffer. next = (i == tmp) ? tail : i; } } public boolean remove(final Object ref) { final Iterator<T> itr = iterator(); while (itr.hasNext()) { if (ref == itr.next()) { itr.remove(); return true; } } return false; } public boolean removeAll(final Collection<?> c) { boolean modified = false; final Iterator<?> itr = iterator(); while (itr.hasNext()) { if (c.contains(itr.next())) { itr.remove(); modified = true; } } return modified; } public boolean retainAll(final Collection<?> c) { boolean modified = false; final Iterator<T> itr = iterator(); while (itr.hasNext()) { if (!c.contains(itr.next())) { itr.remove(); modified = true; } } return modified; } }