
net.sf.eBus.util.IndexPool Maven / Gradle / Ivy
//
// Copyright 2001 - 2005, 2011, 2013 - 2014 Charles W. Rapp
//
// 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 net.sf.eBus.util;
import java.io.Serializable;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
/**
* {@code IndexPool} provides integer index value reuse. When
* you create an {@code IndexPool}, you may specify the minimum
* and maximum allowed values (inclusive). You retrieve the next
* available value using {@link #nextIndex} and return values to
* the pool with {@link #returnIndex}. When the pool is
* exhausted, {@link #nextIndex} throws an
* {@code IllegalStateException}. The application is responsible
* for returning unused indices to the pool by calling
* {@link #returnIndex(int)}. The application is also responsible
* for making sure that {@link #returnIndex(int)} calls contain
* unique indicies and that the same index does not appear in the
* pool multiple times.
*
* {@code IndexPool} uses both an {@link AtomicInteger} and a
* {@link ConcurrentLinkedDeque} (as a stack) to track available
* indices. When {@link #nextIndex()} is called, it first checks
* if the index stack is empty. If it is empty, then
* {@link AtomicInteger#getAndIncrement()} is called to get the
* next unused index. If the returned value is > than the
* maximum allowed index, then {@link IllegalStateException}
* exception is thrown. If the index stack is not empty, then
* the an index is popped off the stack and returned. The idea
* behind this technique is to continually use the same indices
* in the hopes this improves processor cache hits.
*
*
* {@code IndexPool} has no memory between application
* executions. If you need to persist index values between
* executions, use {@link net.sf.eBus.util.IndexCache}.
*
*
* Note: {@code IndexPool} is
* synchronized. It is safe for multiple threads to access the
* same {@code IndexPoool} instance without synchronizing.
*
*
* @see net.sf.eBus.util.IndexCache
*
* @author Charles Rapp
*/
public final class IndexPool
implements Serializable
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* This is eBus version 2.1.0.
*/
private static final long serialVersionUID = 0x050200L;
//-----------------------------------------------------------
// Locals.
//
/**
* The next index value to be returned.
*/
private AtomicInteger mNextIndex;
/**
* The minimum index value which may be used.
*/
private final int mMinIndex;
/**
* The maximum index value which may be used (inclusive).
*/
private final int mMaxIndex;
/**
* Store returned indices here as a stack. The most recently
* returned index is the next available index.
*/
private ConcurrentLinkedDeque mPool;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a pool with the default size.
*
* The default settings are:
*
* -
* Minimum index is zero (this will be the
* first assigned value).
*
* -
* Maximum index is {@code Integer.MAX_VALUE}.
*
*
*/
public IndexPool()
{
this (0, Integer.MAX_VALUE);
} // end of IndexPool()
/**
* Creates a pool with the specified minimum and maximum
* indicies (inclusive).
* @param minIndex the minimum index ({@link #nextIndex} will
* return this value first.)
* @param maxIndex the maximum index (inclusive).
* @throws IllegalArgumentException
* if:
*
* -
* {@code minIndex} is <= zero.
*
* -
* {@code maxIndex} is <= zero.
*
* -
* {@code minIndex} is > {@code maxIndex}.
*
*
*/
public IndexPool(final int minIndex, final int maxIndex)
{
if (minIndex < 0)
{
throw (
new IllegalArgumentException(
"minIndex < 0 (" +
Integer.toString(minIndex) +
")"));
}
else if (maxIndex < 0)
{
throw (
new IllegalArgumentException(
"maxIndex < 0 (" +
Integer.toString(maxIndex) +
")"));
}
else if (minIndex > maxIndex)
{
throw (
new IllegalArgumentException(
"minIndex (" +
Integer.toString(minIndex) +
" > maxIndex (" +
Integer.toString(maxIndex) +
")"));
}
mNextIndex = new AtomicInteger(minIndex);
mMinIndex = minIndex;
mMaxIndex = maxIndex;
mPool = new ConcurrentLinkedDeque<>();
} // end of IndexPool(int, int)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns a textual representation of this index pool.
* @return a textual representation of this index pool.
*/
@Override
public String toString()
{
final Iterator pit;
String separator = "";
final StringBuilder retval = new StringBuilder();
retval.append("minimum index = ").append(mMinIndex)
.append("\nmaximum index = ").append(mMaxIndex)
.append("\ncached indices = {");
for (pit = mPool.iterator();
pit.hasNext();
separator = ", ")
{
retval.append(separator).append(pit.next());
}
retval.append("}\nnext index = ")
.append(mNextIndex.get());
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get methods.
//
/**
* Returns the pool's minimum index.
* @return the pool's minimum index.
*/
public int minIndex()
{
return (mMinIndex);
} // end of minIndex()
/**
* Returns the pool's maximum index.
* @return the pool's maximum index.
*/
public int maxIndex()
{
return (mMaxIndex);
} // end of maxIndex()
//
// end of Get methods.
//-----------------------------------------------------------
/**
* Returns the next available index.
* @return the next available index.
* @exception IllegalStateException
* if all indices are in use and the pool is exhausted. This
* may be due to a failure to return unused indicies to the
* pool.
*/
public int nextIndex()
{
Integer retval = mPool.pollFirst();
if (retval == null)
{
// Generate the next value.
retval = mNextIndex.getAndIncrement();
// Is this pool exhausted?
if (retval> mMaxIndex)
{
// Yes, it is very tired and needs rest.
throw (
new IllegalStateException(
"index pool is exhausted"));
}
}
return (retval);
} // end of nextIndex()
/**
* Puts an index back into the pool for reuse.
*
* Note: this method does not prevent
* returning the same index to the pool multiple times. It
* is the caller's responsibility to make sure that an index
* appears only once in the pool.
*
* @param index Put this index back in the pool.
* @exception IndexOutOfBoundsException
* if {@code index} is less than the minimum index value
* or greater than or equal to the maximum index value.
*/
public void returnIndex(final int index)
{
if (index < mMinIndex)
{
throw (
new IndexOutOfBoundsException(
Integer.toString(index) +
" < minimum index" +
Integer.toString(mMinIndex)));
}
else if (index > mMaxIndex)
{
throw (
new IndexOutOfBoundsException(
Integer.toString(index) +
" > maximum index" +
Integer.toString(mMaxIndex)));
}
else
{
// Put this index into the pool.
mPool.add(index);
}
} // end of returnIndex(int)
/**
* Resets the index pool to its initial state. All indices
* are available for use. Any outstanding indices are
* invalid.
*/
public void reset()
{
mNextIndex.set(mMinIndex);
mPool.clear();
} // end of reset()
} // end of IndexPool