
net.sf.eBus.util.IndexCache Maven / Gradle / Ivy
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library 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 Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2007, 2011, 2013. Charles W. Rapp.
// All Rights Reserved.
//
package net.sf.eBus.util;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Persists a one or more 4-byte, integer index values to a
* specified file. This class allows an application to track an
* index value across executions. An example of this is tracking
* the incoming and outgoing sequence numbers used by a
* communication protocol.
*
* An index cache is created by calling
* {@link #open(java.lang.String) open} and passing in the cache
* file name. If the index cache is currently open, then the
* existing instance is returned. Otherwise, if the file either
* does not exist or is of zero length, then a new index cache is
* created, containing no indices. If the file exists and is of
* non-zero length, the cache file is opened and its subordinate
* indices are read in. The {@link #indexCount()} method returns
* the number of subordinate indices in the cache.
*
*
* An {@link Index} instance is retrieved by calling
* {@link #getIndex(String) getIndex}. A new index is added to the
* cache by calling
* {@link #addIndex(String, long, long, long, boolean) addIndex}.
* An index may have either a positive increment or a negative
* increment. If positive, then the initial index must be ≤ to
* the final index. If negative, then the initial index must by
* ≥ to the final index. The increment may not be zero. An
* existing index is removed from the cache via the method
* {@link #removeIndex(String) removeIndex}.
*
*
* The method {@link Index#nextIndex()} returns the incremented
* index. If the increment index exceeds the final index (either
* > or < depending on whether the increment is > or
* < zero), then the "reset on final index" flag is checked to
* determine what to do next. If this flag is {@code true}, then
* the index is reset to the initial index value and that value
* is returned. If {@code false}, then an
* {@link IndexOutOfBoundsException} is thrown and the index
* can no longer be used.
*
*
* A long is used because that will support all other integer
* types. For smaller integer types, configure the instance
* accordingly.
*
*
* Note: the cache file is not written only when
* the index cache is closed. This class puts adds a thread to
* the
* {@link java.lang.Runtime#addShutdownHook(java.lang.Thread)}.
* This thread closes all open index cache files when the JVM
* shuts down.
*
*
* This class does not support sharing the index cache
* file between simultaneously executing applications. Such use
* will corrupt the index file and result in undetermined
* results. Neither {@code IndexCache} nor
* {@code IndexCache.Index} classes are thread-safe. If there is
* a need to share an {@code IndexCache} instance among threads,
* then the application performs the appropriate synchronization.
* The static data member {@link #open(String)} and
* {@link #isCacheOpen(java.lang.String)} are thread-safe.
*
*
* If you want unique, reusable index values that do not need
* to be persisted between application executions, then use
* {@link net.sf.eBus.util.IndexPool}.
*
*
* @see net.sf.eBus.util.IndexPool
*
* @author Charles Rapp
*/
public final class IndexCache
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* The default initial index is zero.
*/
public static final long DEFAULT_INITIAL_INDEX = 0L;
/**
* The default final index is {@code Long.MAX_VALUE}.
*/
public static final long DEFAULT_FINAL_INDEX =
Long.MAX_VALUE;
/**
* The default index increment is one.
*/
public static final long DEFAULT_INCREMENT = 1L;
/**
* Automatic index reset is turned off by default.
*/
public static final boolean DEFAULT_AUTO_RESET = false;
/**
* An index name may be at most 20 characters long.
*/
public static final int MAXIMUM_INDEX_NAME_LENGTH = 20;
/**
* Force file contents to be asynchronously written to the
* device.
*/
private static final String ACCESS_MODE = "rwd";
/**
* An existing cache file must contain at least 4 bytes which
* is the cache size.
*/
private static final long MINIMUM_FILE_LENGTH = 4L;
/**
* The first index is placed after the cache size at this
* offset.
*/
private static final long INDEX_OFFSET = 4L;
/**
* Each index takes up 54 bytes:
*
* -
* Index name: 21 bytes (1 byte length + 20 bytes name)
*
* -
* Initial index: 8 bytes
*
* -
* Final index: 8 bytes
*
* -
* Increment: 8 bytes
*
* -
* Reset flag: 1 byte
*
* -
* Current index: 8 bytes
*
*
* Total: 54 bytes
*/
private static final long INDEX_SIZE = 54L;
private static final byte PADDING_VALUE = (byte) 0;
private static final byte[] NAME_PADDING =
{
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE,
PADDING_VALUE
};
private static final String CORRUPT_INDEX_CACHE =
"corrupt index cache";
//-----------------------------------------------------------
// Statics.
//
/**
* Maps the cache file name to its encapsulating index
* cache instance.
*/
private static final Map sCacheMap =
new HashMap<>();
/**
* Use this lock to protected the index cache map.
*/
private static final Lock sCacheMutex = new ReentrantLock();
//-----------------------------------------------------------
// Locals.
//
/**
* The indices within this cache. Maps index name to the
* index instance.
*/
private final Map mIndices;
/**
* The cache file name.
*/
private final String mCacheFileName;
/**
* The cache file random access instance.
*/
private final RandomAccessFile mCacheFile;
/**
* Track when any the cache was last modified.
*/
private final Date mLastModified;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* The index cache with its individual indices and cache
* file.
* @param indices the initial cache indices. May be empty.
* @param cacheFileName the cache file name as supplied by
* user.
* @param cacheFile the random access file instance.
* @param lastModified the file last modified date.
*/
private IndexCache(final Map indices,
final String cacheFileName,
final RandomAccessFile cacheFile,
final Date lastModified)
{
mIndices = indices;
mCacheFileName = cacheFileName;
mCacheFile = cacheFile;
mLastModified = lastModified;
} // end of IndexCache(Map<>, String, RandomAccessFile, Date)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get methods.
//
/**
* Returns the cache file name as originally supplied by
* user.
* @return the cache file name.
*/
public String cacheFileName()
{
return (mCacheFileName);
} // end of cacheFileName()
/**
* Returns the timestamp when the cache was last modified.
* This value is set to the cache file last modified date
* when opened or to the current date and time when created.
* @return the timestamp when the cache was last modified.
*/
public Date lastModified()
{
return (new Date(mLastModified.getTime()));
} // end of lastModified()
/**
* Returns {@code true} if this cache has no indices and
* {@code false} if it has at least one index.
* @return {@code true} if this cache has no indices and
* {@code false} if it has at least one index.
*/
public boolean isEmpty()
{
return (mIndices.isEmpty());
} // end of isEmpty()
/**
* Returns the number of indices in this cache.
* @return the number of indices in this cache.
*/
public int indexCount()
{
return (mIndices.size());
} // end of indexCount()
/**
* Returns a list of current index names. The returned list
* is a copy of the actual list and so may be modified
* without impacting the actual list.
* @return a list of current index names.
*/
public List indexNames()
{
return (new ArrayList<>(mIndices.keySet()));
} // end of indexNames()
/**
* Returns {@code true} if this cache contains the named
* index and {@code false} otherwise.
* @param indexName the index name.
* @return {@code true} if this cache contains the named
* index and {@code false} otherwise.
* @see #getIndex(String)
* @see #addIndex(String, long, long, long, boolean)
* @see #removeIndex(String)
*/
public boolean containsIndex(final String indexName)
{
return (mIndices.containsKey(indexName));
} // end of containsIndex(String)
/**
* Returns the named index instance. May return {@code null}.
* @param indexName the index name.
* @return the specified named index instance.
* @see #containsIndex(String)
* @see #addIndex(String, long, long, long, boolean)
* @see #removeIndex(String)
*/
public Index getIndex(final String indexName)
{
return (mIndices.get(indexName));
} // end of getIndex(String)
/**
* Returns {@code true} if the named index cache is open and
* {@code false} if it is not.
* @param cacheName the index cache file name.
* @return {@code true} if the named index cache is open and
* {@code false} if it is not.
*/
public static boolean isCacheOpen(final String cacheName)
{
boolean retcode = false;
sCacheMutex.lock();
try
{
retcode = sCacheMap.containsKey(cacheName);
}
finally
{
sCacheMutex.unlock();
}
return (retcode);
} // end of isCacheOpen(String)
//
// end of Get methods.
//-----------------------------------------------------------
/**
* Returns the new index added to the cache. Note that
* {@code increment} may be either > or < zero and
* that {@code initialIndex} may equal {@code finalIndex}.
* @param indexName the index name. Must be unique within
* {@code this IndexCache}.
* @param initialIndex the index initial value.
* @param finalIndex the index maximum value. The index
* will not exceed this value.
* @param increment increment the current value by this
* amount to get the next index value. The increment may
* be either > zero or < zero.
* @param resetFlag if {@code true}, then reset the index
* to the initial value when the final value is exceeded.
* @return the newly added index.
* @throws IllegalArgumentException
* if:
*
* -
* if {@code indexName} is {@code null}, an empty string
* or longer than {@link #MAXIMUM_INDEX_NAME_LENGTH},
*
* -
* if {@code indexName} is not unique within
* {@code this IndexCache} instance,
*
* -
* {@code increment} is zero,
*
* -
* {@code initialIndex} is > {@code finalIndex} and
* {@code increment} is > zero or
*
* -
* {@code initialIndex} is < {@code finalIndex} and
* {@code increment} is < zero
*
*
*/
public Index addIndex(final String indexName,
final long initialIndex,
final long finalIndex,
final long increment,
final boolean resetFlag)
{
Index retval = null;
if (indexName == null)
{
throw (
new IllegalArgumentException("null indexName"));
}
else if (indexName.length() == 0)
{
throw (
new IllegalArgumentException("empty indexName"));
}
else if (indexName.length() > MAXIMUM_INDEX_NAME_LENGTH)
{
throw (
new IllegalArgumentException(
"indexName too long"));
}
else if (mIndices.containsKey(indexName))
{
throw (
new IllegalArgumentException(
"indexName not unique"));
}
else if (increment == 0L)
{
throw (
new IllegalArgumentException("zero increment"));
}
else if (increment > 0L && initialIndex > finalIndex)
{
throw (
new IllegalArgumentException(
"increment > 0 and initialIndex > finalIndex"));
}
else if (increment < 0L && initialIndex < finalIndex)
{
throw (
new IllegalArgumentException(
"increment < 0 and initialIndex < finalIndex"));
}
else
{
retval = new Index(indexName,
initialIndex,
finalIndex,
increment,
resetFlag,
initialIndex);
mIndices.put(indexName, retval);
}
return (retval);
} // end of addIndex(long, long, long. boolean)
/**
* Returns {@code true} if the named index was removed and
* {@code false} if the index is unknown.
* @param indexName remove the named index at this position.
* @return {@code true} if the named index was removed and
* {@code false} if the index is unknown.
*/
public boolean removeIndex(final String indexName)
{
boolean retcode = mIndices.containsKey(indexName);
if (retcode)
{
mIndices.remove(indexName);
}
return (retcode);
} // end of removeIndex(String)
/**
* Resets all indices to their respective initial values.
*/
public void resetAllIndices()
{
mIndices.values().stream().forEach(Index::resetIndex);
} // end of resetAllIndices()
/**
* Removes all indices from the cache leaving the cache
* empty.
*/
public void clearIndices()
{
mIndices.clear();
} // end of clearIndices()
/**
* Closes the cache file and removes this index cache from
* the map. This index cache must be re-opened before it may
* be used again.
* @see #open(java.lang.String)
*/
public void close()
{
doClose();
sCacheMutex.lock();
try
{
sCacheMap.remove(mCacheFileName);
}
finally
{
sCacheMutex.unlock();
}
} // end of close()
/**
* Returns a textual representation of this index cache.
* @return a textual representation of this index cache.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append("cache file = ").append(mCacheFileName)
.append("\n indices =");
if (mIndices.isEmpty())
{
retval.append(" (none)");
}
else
{
mIndices.values().stream().
forEach(
index ->
retval.append('\n').append(index));
}
return (retval.toString());
} // end of toString()
/**
* Returns the {@code IndexCache} instance associated with
* {@code cacheFileName}. If the instance is already open,
* that instance is returned. Otherwise the cache file's
* existence is tested. If the file does not exist or is of
* size zero, then returns a new, empty {@code IndexCache}
* instance. Otherwise, the cache file is opened for reading
* and writing, the index information is read in and the
* instance is created from the data.
* @param cacheFileName read in the index data from this
* file.
* @return the index cache instance associated with the
* file name.
* @exception IllegalArgumentException
* if {@code cacheFileName} is either {@code null} or empty.
* @exception IOException
* if {@code cacheFileName} exists but contains corrupted
* data.
*/
public static IndexCache open(final String cacheFileName)
throws IOException
{
IndexCache retval = null;
if (cacheFileName == null)
{
throw (
new IllegalArgumentException(
"null cacheFileName"));
}
else if (cacheFileName.length() == 0)
{
throw (
new IllegalArgumentException(
"empty cacheFileName"));
}
sCacheMutex.lock();
try
{
File fInfo = new File(cacheFileName);
// Is there already an instance of this index cache?
if (sCacheMap.containsKey(cacheFileName))
{
// Yes. Return that instance.
retval = sCacheMap.get(cacheFileName);
}
// No, the index cache is not in the map.
// Does the file exist or is it an empty file?
else if (!fInfo.exists() || fInfo.length() == 0L)
{
// No. Create a new, empty cache file.
retval = newCache(cacheFileName);
sCacheMap.put(cacheFileName, retval);
}
// Yes, the file exists.
// Does it contain at leat the minimum size?
else if (fInfo.length() < MINIMUM_FILE_LENGTH)
{
// No, this file is not valid.
throw (
new IOException(
String.format("%s invalid, %,d bytes",
cacheFileName,
MINIMUM_FILE_LENGTH)));
}
// Yes, the file is at least the minimum size.
// Read in the cache file.
else
{
retval = openCache(cacheFileName, fInfo);
sCacheMap.put(cacheFileName, retval);
}
}
finally
{
sCacheMutex.unlock();
}
return (retval);
} // end of open(String)
/**
* Returns a new, empty index cache backed by the given file.
* @param fn creates this non-existent file.
* @return a new, empty index cache backed by the given file.
* @throws IOException
* if an I/O error occurs opening the file.
*/
private static IndexCache newCache(final String fn)
throws IOException
{
final RandomAccessFile fs =
new RandomAccessFile(fn, ACCESS_MODE);
final Map indices =
new HashMap<>();
return (new IndexCache(indices, fn, fs, new Date()));
} // end of newCache(File)
/**
* Returns the index cache stored in the file.
* @param fn the cache file name.
* @param fInfo the cache file.
* @return a newly opened index cache.
* @throws IOException
* if an I/O error occurs reading in the cache file.
*/
private static IndexCache openCache(final String fn,
final File fInfo)
throws IOException
{
final IndexCache retval;
try (RandomAccessFile fs =
new RandomAccessFile(fn, ACCESS_MODE))
{
final Map indices = new HashMap<>();
final int indexCount = fs.readInt();
final long fileSize =
(INDEX_OFFSET + (indexCount * INDEX_SIZE));
int i;
Index index;
// If the index count is negative or the calculated
// file size is larger than the actual file size,
// then something is wrong with the file.
if (indexCount < 0 || fileSize > fInfo.length())
{
throw (new IOException(CORRUPT_INDEX_CACHE));
}
for (i = 0; i < indexCount; ++i)
{
index = readIndex(fs);
indices.put(index.name(), index);
}
retval =
new IndexCache(indices,
fn,
fs,
new Date(fInfo.lastModified()));
}
return (retval);
} // end of openCache(File)
/**
* Returns the index read from the cache file at the current
* file position.
* @param fs the cache file.
* @return index instance read from the cache file.
* @throws IOException
* if an I/O error occurred reading in the index.
*/
private static Index readIndex(final RandomAccessFile fs)
throws IOException
{
final String indexName = readName(fs);
final long initialIndex = fs.readLong();
final long finalIndex = fs.readLong();
final long increment = fs.readLong();
final boolean resetFlag = fs.readBoolean();
final long currentIndex = fs.readLong();
// Verify the parameters.
if (increment == 0L ||
(increment > 0L && initialIndex > finalIndex) ||
(increment < 0L && initialIndex < finalIndex) ||
(increment > 0L && currentIndex > finalIndex) ||
(increment < 0L && currentIndex < finalIndex))
{
throw (new IOException(CORRUPT_INDEX_CACHE));
}
return (new Index(indexName,
initialIndex,
finalIndex,
increment,
resetFlag,
currentIndex));
} // end of readIndex(RandomAccessFile)
/**
* Performs the actual work of closing an index cache file.
* Writes all cached indices to file before closing.
*/
private void doClose()
{
try
{
// Write the index count.
mCacheFile.seek(0L);
mCacheFile.writeInt(mIndices.size());
// Have each existing index write out its current
// value to the cache.
for (Index index : mIndices.values())
{
index.store(mCacheFile);
}
// Clear out the index map as it may no longer be
// used.
mIndices.clear();
}
catch (IOException ioex)
{
// Ignore.
}
finally
{
try
{
mCacheFile.close();
}
catch (IOException ioex)
{
// Ignore.
}
}
} // end of doClose()
/**
* Returns an index name read from the random access file at
* the file's current position.
* @param fs read from this file.
* @return the index name.
* @throws IOException
* if an I/O error occurred reading the index name.
*/
private static String readName(final RandomAccessFile fs)
throws IOException
{
final int length = fs.read();
String retval = null;
if (length <= 0 || length > MAXIMUM_INDEX_NAME_LENGTH)
{
throw (new IOException(CORRUPT_INDEX_CACHE));
}
else
{
final byte[] data =
new byte[MAXIMUM_INDEX_NAME_LENGTH];
// Read in the name bytes *and* the padding.
// The padding will be ignored when the string is
// instantiated.
if (fs.read(data) < 0)
{
throw (
new IOException("error reading index name"));
}
retval = new String(data, StandardCharsets.US_ASCII);
}
return (retval);
} // end of readName(RandomAccessFile, String)
/**
* Writes the index name to the file stream. Writes the
* name length first, then the name and pads the remaining
* to the maximum index name length.
* @param name write this index name to the file
* @param fs the random access file.
* @throws IOException
* if an I/O error occurs writing the index name to the file.
*/
@SuppressWarnings({"java:S3398"})
private static void writeName(final String name,
final RandomAccessFile fs)
throws IOException
{
final int length = name.length();
final int paddingLength =
(MAXIMUM_INDEX_NAME_LENGTH - length);
fs.write(length);
fs.write(name.getBytes(StandardCharsets.US_ASCII));
// If the name is less than the maximum length, then
// pad out the remaining with 0 bytes.
if (paddingLength > 0)
{
fs.write(NAME_PADDING, 0, paddingLength);
}
} // end of writeName(String, RandomAccessFile)
//---------------------------------------------------------------
// Inner classes.
//
/**
* Tracks the individual index parameters and its location
* within the cache file. {@code Index} instances are
* accessed via {@link IndexCache#getIndex(String)}. This
* index is incremented by the method {@link #nextIndex()}.
* This index can be reset to its initial value by the method
* {@link #resetIndex()}.
*/
public static final class Index
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* The index name. Must be unique within the cache.
*/
private final String mName;
/**
* The index initial value. If the reset-upon-final flag
* is {@code true}, then the index is set to this value
* when the final index is reached.
*/
private final long mInitialIndex;
/**
* The final index value. The index must either always
* be ≤ this value if the increment is > 0 or
* ≥ this value if increment is < 0.
*/
private final long mFinalIndex;
/**
* The index increment for determining the next increment
* value.
*/
private final long mIncrement;
/**
* If this flag is {@code true}, then reset the index
* to the initial value when the final value is passed.
*/
private final boolean mResetUponFinalFlag;
/**
* The current index value. Initialized to the initial
* index.
*/
private long mCurrentIndex;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private Index(final String name,
final long initialIndex,
final long finalIndex,
final long increment,
final boolean resetFlag,
final long currentIndex)
{
mName = name;
mInitialIndex = initialIndex;
mFinalIndex = finalIndex;
mIncrement = increment;
mResetUponFinalFlag = resetFlag;
mCurrentIndex = currentIndex;
} // end of Index(...)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get methods.
//
/**
* Returns the index name.
* @return the index name.
*/
public String name()
{
return (mName);
} // end of name()
/**
* Returns the initial cache index.
* @return initial cache index.
*/
public long initialIndex()
{
return (mInitialIndex);
} // end of initialIndex()
/**
* Returns the increment used to obtain the next cached
* index.
* @return index increment.
*/
public long increment()
{
return (mIncrement);
} // end of increment()
/**
* Returns the maximum cached index.
* @return maximum cached index.
*/
public long finalIndex()
{
return (mFinalIndex);
} // end of finalIndex()
/**
* Returns the current cached index.
* @return current cached index.
*/
public long currentIndex()
{
return (mCurrentIndex);
} // end of currentIndex()
/**
* Returns {@code true} if the cache automatically resets
* to the initial index once the final index is reached.
* @return {@code true} if the cache automatically resets
* to the initial index.
*/
public boolean doesResetUponFinalIndex()
{
return (mResetUponFinalFlag);
} // end of doesResetUponFinalIndex()
//
// end of Get methods.
//-------------------------------------------------------
/**
* Returns the current index value. If the current index
* value exceeds the final index value and if the
* "reset upon final index" flag is {@code true}, then
* the initial index value is returned. If the flag is
* {@code false}, an
* {@link java.lang.IndexOutOfBoundsException} is thrown.
* Otherwise the current index is incremented prior to
* returning but this incremented value is not
* returned.
* @return the current index value.
* @throws IndexOutOfBoundsException
* if the incremented index exceeds the final index value
* and the index does not reset back to the initial
* value.
*/
public long nextIndex()
{
long retval = mCurrentIndex;
mCurrentIndex += mIncrement;
if ((retval > mFinalIndex && mIncrement > 0L) ||
(retval < mFinalIndex && mIncrement < 0L))
{
if (mResetUponFinalFlag)
{
retval = mInitialIndex;
}
else
{
throw (
new IndexOutOfBoundsException(
String.format(
"cannot increment beyond final index (%,d)",
mFinalIndex)));
}
}
mCurrentIndex = (retval + mIncrement);
return (retval);
} // end of nextIndex()
/**
* Sets the current index to the given value.
* @param index the new current index.
* @throws IllegalArgumentException
* if {@code index} is not between the index initial
* and final values.
*/
public void currentIndex(final long index)
{
if ((mIncrement > 0L &&
(index < mInitialIndex ||
index > mFinalIndex)) ||
(mIncrement < 0L &&
(index > mInitialIndex ||
index < mFinalIndex)))
{
throw (
new IllegalArgumentException(
"index outside initial, final interval"));
}
else
{
mCurrentIndex = index;
}
} // end of currentIndex(long)
/**
* Resets the current index to the initial value and
* returns the new current index.
* @return the newly reset current index.
*/
public long resetIndex()
{
mCurrentIndex = mInitialIndex;
return (mCurrentIndex);
} // end of resetIndex()
/**
* Returns the textual representation of this index.
* @return the textual representation of this index.
*/
@Override
public String toString()
{
final String retval;
try (Formatter output = new Formatter())
{
output.format("[%s]%n", mName)
.format(" initial index = %,d%n",
mInitialIndex)
.format(" final index = %,d%n",
mFinalIndex)
.format(" increment = %,d%n",
mIncrement)
.format(" current index = %,d%n",
mCurrentIndex)
.format("reset upon final = %b",
mResetUponFinalFlag);
retval = output.toString();
}
return (retval);
} // end of toString()
/**
* Stores the all index data to the file at the current
* position.
* @param fs Write to this random access file.
* @throws IOException
* if an error occurs writing this index to the file.
*/
private void store(final RandomAccessFile fs)
throws IOException
{
writeName(mName, fs);
fs.writeLong(mInitialIndex);
fs.writeLong(mFinalIndex);
fs.writeLong(mIncrement);
fs.writeBoolean(mResetUponFinalFlag);
fs.writeLong(mCurrentIndex);
} // end of store(RandomAccessFile)
} // end of class Index
/**
* When the JVM shuts down, close all open index caches.
*/
private static final class CacheShutdown
extends Thread
{
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private CacheShutdown()
{}
//
// end of Constructors.
//-------------------------------------------------------
@Override
public void run()
{
final List caches = new ArrayList<>();
sCacheMutex.lock();
try
{
caches.addAll(sCacheMap.values());
sCacheMap.clear();
}
finally
{
sCacheMutex.unlock();
}
caches.stream().forEach(IndexCache::doClose);
} // end of run()
//-----------------------------------------------------------
// Member data.
//
} // end of class CacheShutdown
static
{
(Runtime.getRuntime()).addShutdownHook(
new CacheShutdown());
} // end of static
} // end of IndexCache
//
// CHANGE LOG
// $Log: IndexCache.java,v $
// Revision 1.2 2007/02/23 13:38:32 charlesr
// Corrected javadoc comments.
//
// Revision 1.1 2007/02/12 20:43:42 charlesr
// Initial revision
//