Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
ucar.nc2.util.cache.FileCache Maven / Gradle / Ivy
Go to download
The NetCDF-Java Library is a Java interface to NetCDF files,
as well as to many other types of scientific data formats.
/*
* Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package ucar.nc2.util.cache;
import net.jcip.annotations.ThreadSafe;
import net.jcip.annotations.GuardedBy;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import java.io.IOException;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.util.CancelTask;
import ucar.nc2.util.Misc;
/**
* Keep cache of open FileCacheable objects, for example NetcdfFile.
* The FileCacheable object typically contains a RandomAccessFile object that wraps a system resource like a file handle.
* These are left open when the FileCacheable is in the cache. The maximum number of these is bounded, though not strictly.
* A cleanup routine reduces cache size to a minimum number. This cleanup is called periodically in a background thread,
* and also when the maximum cache size is reached.
*
* The FileCacheable object must not be modified.
* The hashKey must uniquely define the FileCacheable object.
* The location must be usable in a FileFactory.open().
* If the FileCacheable is acquired from the cache (ie already open), getLastModified() is used to see if it has changed,
* and is discarded if so.
* Make sure you call shutdown() when exiting the program, in order to shut down the cleanup thread.
*
*
* Normal usage is through the NetcdfDataset interface:
*
* NetcdfDataset.initNetcdfFileCache(...); // on application startup
* ...
* NetcdfFile ncfile = null;
* try {
* ncfile = NetcdfDataset.acquireFile(location, cancelTask);
* ...
* } finally {
* if (ncfile != null) ncfile.close();
* }
* ...
* NetcdfDataset.shutdown(); // when terminating the application
*
* All methods are thread safe.
* Cleanup is done automatically in a background thread, using LRU algorithm.
*
* @author caron
* @since May 30, 2008
*/
@ThreadSafe
public class FileCache implements FileCacheIF {
static protected final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FileCache.class);
static protected final org.slf4j.Logger cacheLog = org.slf4j.LoggerFactory.getLogger("cacheLogger");
static private ScheduledExecutorService exec;
static boolean trackAll = false;
static boolean debug = false;
static boolean debugPrint = false;
static boolean debugCleanup = false;
/**
* You must call shutdown() to shut down the background threads in order to get a clean process shutdown.
*/
public static synchronized void shutdown() {
if (exec != null)
exec.shutdown();
exec = null;
}
/////////////////////////////////////////////////////////////////////////////////////////
protected String name;
protected final int softLimit, minElements, hardLimit, period;
private final boolean wantsCleanup;
private final AtomicBoolean disabled = new AtomicBoolean(false); // cache is disabled
protected final AtomicBoolean hasScheduled = new AtomicBoolean(false); // a cleanup is scheduled
protected final ConcurrentHashMap cache; // unique files (by key, often = filename)
protected final ConcurrentHashMap files; // list of all files in the cache
// debugging and stats
protected final AtomicInteger cleanups = new AtomicInteger(); // how many cleanups
protected final AtomicInteger hits = new AtomicInteger();
protected final AtomicInteger miss = new AtomicInteger();
protected ConcurrentHashMap track;
/**
* Constructor.
*
* @param minElementsInMemory keep this number in the cache
* @param maxElementsInMemory trigger a cleanup if it goes over this number.
* @param period (secs) do periodic cleanups every this number of seconds.
*/
public FileCache(int minElementsInMemory, int maxElementsInMemory, int period) {
this("", minElementsInMemory, maxElementsInMemory, -1, period);
}
/**
* Constructor.
*
* @param minElementsInMemory keep this number in the cache
* @param softLimit trigger a cleanup if it goes over this number.
* @param hardLimit if > 0, never allow more than this many elements. This causes a cleanup to be done in the calling thread.
* @param period if > 0, do periodic cleanups every this number of seconds.
*/
public FileCache(int minElementsInMemory, int softLimit, int hardLimit, int period) {
this("", minElementsInMemory, softLimit, hardLimit, period);
}
/**
* Constructor.
*
* @param name of file cache
* @param minElementsInMemory keep this number in the cache
* @param softLimit trigger a cleanup if it goes over this number.
* @param hardLimit if > 0, never allow more than this many elements. This causes a cleanup to be done in the calling thread.
* @param period if > 0, do periodic cleanups every this number of seconds.
*/
public FileCache(String name, int minElementsInMemory, int softLimit, int hardLimit, int period) {
this.name = name;
this.minElements = minElementsInMemory;
this.softLimit = softLimit;
this.hardLimit = hardLimit;
this.period = period;
cache = new ConcurrentHashMap<>(2 * softLimit, 0.75f, 8);
files = new ConcurrentHashMap<>(4 * softLimit, 0.75f, 8);
wantsCleanup = period > 0;
if (wantsCleanup) {
getExec().scheduleAtFixedRate(new CleanupTask(), period, period, TimeUnit.SECONDS);
if (cacheLog.isDebugEnabled())
cacheLog.debug("FileCache " + name + " cleanup every " + period + " secs");
}
if (trackAll)
track = new ConcurrentHashMap<>(5000);
}
private static synchronized ScheduledExecutorService getExec() {
if (exec == null) exec = Executors.newSingleThreadScheduledExecutor();
return exec;
}
/**
* Disable the cache, and force release all files.
* You must still call shutdown() before exiting the application.
*/
@Override
public void disable() {
this.disabled.set(true);
clearCache(true);
}
/**
* Enable the cache, with the current set of parameters.
*/
@Override
public void enable() {
this.disabled.set(false);
}
/**
* Acquire a FileCacheable, and lock it so no one else can use it.
* call FileCacheable.close() when done.
*
* @param factory use this factory to open the file; may not be null
* @param location file location, also used as the cache name, will be passed to the NetcdfFileFactory
* @param cancelTask user can cancel, ok to be null.
* @return NetcdfFile corresponding to location.
* @throws IOException on error
*/
public FileCacheable acquire(FileFactory factory, String location, ucar.nc2.util.CancelTask cancelTask) throws IOException {
return acquire(factory, location, location, -1, cancelTask, null);
}
@Override
public FileCacheable acquire(FileFactory factory, String location) throws IOException {
return acquire(factory, location, location, -1, null, null);
}
/**
* Acquire a FileCacheable from the cache, and lock it so no one else can use it.
* If not already in cache, open it the FileFactory, and put in cache.
*
* App should call FileCacheable.close() when done, and the file is then released instead of closed.
*
* If cache size goes over maxElement, then immediately (actually in 100 msec) schedule a cleanup in a background thread.
* This means that the cache should never get much larger than maxElement, unless you have them all locked.
*
* @param factory use this factory to open the file if not in the cache; may not be null
* @param hashKey unique key for this file. If null, the location will be used
* @param location file location, may also used as the cache name, will be passed to the NetcdfFileFactory
* @param buffer_size RandomAccessFile buffer size, if <= 0, use default size
* @param cancelTask user can cancel, ok to be null.
* @param spiObject passed to the factory if object needs to be recreated
* @return FileCacheable corresponding to location.
* @throws IOException on error
*/
@Override
public FileCacheable acquire(FileFactory factory, Object hashKey, String location,
int buffer_size, CancelTask cancelTask, Object spiObject) throws IOException {
if (null == hashKey) hashKey = location;
if (null == hashKey) throw new IllegalArgumentException();
Tracker t = null;
if (trackAll) {
t = new Tracker(hashKey);
Tracker prev = track.putIfAbsent(hashKey, t);
if (prev != null) t = prev;
}
FileCacheable ncfile = acquireCacheOnly(hashKey);
if (ncfile != null) {
hits.incrementAndGet();
if (t != null) t.hit++;
return ncfile;
}
miss.incrementAndGet();
if (t != null) t.miss++;
// open the file
ncfile = factory.open(location, buffer_size, cancelTask, spiObject);
if (cacheLog.isDebugEnabled())
cacheLog.debug("FileCache " + name + " acquire " + hashKey + " " + ncfile.getLocation());
if (debugPrint) System.out.println(" FileCache " + name + " acquire " + hashKey + " " + ncfile.getLocation());
// user may have canceled
if ((cancelTask != null) && (cancelTask.isCancel())) {
if (ncfile != null) ncfile.close();
return null;
}
if (disabled.get()) return ncfile;
// see if cache element already exists
// cant use putIfAbsent, because we cant create the CacheElement until we know if doesnt exist
CacheElement elem;
synchronized (cache) {
elem = cache.get(hashKey);
if (elem == null) cache.put(hashKey, new CacheElement(ncfile, hashKey)); // new element
}
// already exists, add to list
if (elem != null) {
synchronized (elem) {
elem.addFile(ncfile); // add to existing list
}
}
// increment the number of files in the cache
//int count = counter.incrementAndGet();
// do we need a cleanup ??
boolean needHard = false;
boolean needSoft = false;
synchronized (hasScheduled) {
if (!hasScheduled.get()) {
int count = files.size();
if ((count > hardLimit) && (hardLimit > 0)) {
needHard = true;
hasScheduled.getAndSet(true); // tell other threads not to schedule another cleanup
} else if ((count > softLimit) && (softLimit > 0)) { // && wantsCleanup) { //
hasScheduled.getAndSet(true); // tell other threads not to schedule another cleanup
needSoft = true;
}
}
}
if (needHard) {
if (debugCleanup)
System.out.println("CleanupTask due to hard limit time=" + new Date().getTime()); // +" Thread="+Thread.currentThread().hashCode()
cleanup(hardLimit);
} else if (needSoft) {
getExec().schedule(new CleanupTask(), 100, TimeUnit.MILLISECONDS); // immediate cleanup in 100 msec
if (debugCleanup) System.out.println("CleanupTask scheduled due to soft limit time=" + new Date());
}
return ncfile;
}
/**
* Try to find a file in the cache.
*
* @param hashKey used as the key.
* @return file if its in the cache, null otherwise.
*/
private FileCacheable acquireCacheOnly(Object hashKey) {
if (disabled.get()) return null;
// see if its in the cache
CacheElement wantCacheElem = cache.get(hashKey);
if (wantCacheElem == null) return null; // not found in cache
CacheElement.CacheFile want = null;
synchronized (wantCacheElem) { // synch in order to traverse the list
for (CacheElement.CacheFile file : wantCacheElem.list) {
if (file.isLocked.compareAndSet(false, true)) {
want = file;
break;
}
}
}
if (want == null) return null; // no unlocked file in cache
/* DISABLED 2/26/2013 JCARON use getLastModified()
sync the file when you want to use it again : needed for grib growing index, netcdf-3 record growing, etc
if (want != null) {
try {
boolean changed = ncfile.sync();
if (cacheLog.isDebugEnabled())
cacheLog.debug("FileCache " + name + " aquire from cache " + hashKey + " " + ncfile.getLocation() + " changed = " + changed);
if (debugPrint)
System.out.println(" FileCache " + name + " aquire from cache " + hashKey + " " + ncfile.getLocation() + " changed = " + changed);
} catch (IOException e) {
log.error("FileCache " + name + " synch failed on " + ncfile.getLocation() + " " + e.getMessage());
return null;
}
} */
// check if modified, remove if so
if (want.ncfile != null) {
long lastModified = want.ncfile.getLastModified();
boolean changed = lastModified != want.lastModified;
if (cacheLog.isDebugEnabled() && changed)
cacheLog.debug("FileCache " + name + ": acquire from cache " + hashKey + " " + want.ncfile.getLocation() + " was changed; discard");
if (changed) {
want.remove();
files.remove(want.ncfile);
want.ncfile.setFileCache(null);
try {
want.ncfile.close();
} catch (IOException e) {
log.error("close failed on "+want.ncfile.getLocation(), e);
}
want.ncfile = null;
}
}
return want.ncfile;
}
/**
* Remove all instances of object from the cache
* @param hashKey the object
*/
public void remove(Object hashKey) {
if (disabled.get()) return;
// see if its in the cache
CacheElement wantCacheElem = cache.get(hashKey);
if (wantCacheElem == null) return;
synchronized (wantCacheElem) { // synch in order to traverse the list
for (CacheElement.CacheFile want : wantCacheElem.list) {
files.remove(want.ncfile);
want.ncfile.setFileCache(null); // unhook the caching
try {
want.ncfile.close(); // really close the file
log.debug("close "+want.ncfile.getLocation());
} catch (IOException e) {
log.error("close failed on "+want.ncfile.getLocation(), e);
}
want.ncfile = null;
}
wantCacheElem.list.clear();
}
cache.remove(hashKey);
}
/**
* Release the file. This unlocks it, updates its lastAccessed date.
* Normally applications need not call this, just close the file as usual.
*
* @param ncfile release this file.
* @throws IOException if file not in cache.
*/
@Override
public void release(FileCacheable ncfile) throws IOException {
if (ncfile == null) return;
if (disabled.get()) {
ncfile.setFileCache(null); // prevent infinite loops
ncfile.close();
return;
}
// find it in the file cache
CacheElement.CacheFile file = files.get(ncfile); // using hashCode of the FileCacheable
if (file != null) {
if (!file.isLocked.get()) {
Exception e = new Exception("Stack trace");
cacheLog.warn("FileCache " + name + " release " + ncfile.getLocation() + " not locked; hash= "+ncfile.hashCode(), e);
}
file.lastAccessed = System.currentTimeMillis();
file.countAccessed++;
file.isLocked.set(false);
if (cacheLog.isDebugEnabled()) cacheLog.debug("FileCache " + name + " release " + ncfile.getLocation()+"; hash= "+ncfile.hashCode());
if (debugPrint) System.out.println(" FileCache " + name + " release " + ncfile.getLocation());
return;
}
throw new IOException("FileCache " + name + " release does not have file in cache = " + ncfile.getLocation());
}
/*
* Remove all copies of the file from the cache.
*
* @param ncfile remove this file.
* @throws IOException if file not in cache.
*
public void remove(FileCacheable ncfile) throws IOException {
if (ncfile == null) return;
if (disabled.get()) {
ncfile.setFileCache(null); // prevent infinite loops
ncfile.close();
return;
}
// find it in the file cache
CacheElement.CacheFile file = files.get(ncfile); // using hashCode of the FileCacheable
if (file != null) {
if (!file.isLocked.get())
cacheLog.warn("FileCache " + name + " release " + ncfile.getLocation() + " not locked");
file.lastAccessed = System.currentTimeMillis();
file.countAccessed++;
file.isLocked.set(false);
if (cacheLog.isDebugEnabled()) cacheLog.debug("FileCache " + name + " release " + ncfile.getLocation());
if (debugPrint) System.out.println(" FileCache " + name + " release " + ncfile.getLocation());
return;
}
throw new IOException("FileCache " + name + " release does not have file in cache = " + ncfile.getLocation());
} */
// debug
public String getInfo(FileCacheable ncfile) throws IOException {
if (ncfile == null) return "";
// find it in the file cache
CacheElement.CacheFile file = files.get(ncfile);
if (file != null) {
return "File is in cache= " + file;
}
return "File not in cache";
}
// debug
Map getCache() {
return cache;
}
/**
* Remove all cache entries.
*
* @param force if true, remove them even if they are currently locked.
*/
public synchronized void clearCache(boolean force) {
List deleteList = new ArrayList<>(2 * cache.size());
if (force) {
cache.clear(); // deletes everything from the cache
deleteList.addAll(files.values()); // add everything to the delete list
files.clear();
// counter.set(0);
} else {
// add unlocked files to the delete list, remove from files hash
Iterator iter = files.values().iterator();
while (iter.hasNext()) {
CacheElement.CacheFile file = iter.next();
if (file.isLocked.compareAndSet(false, true)) {
file.remove(); // remove from the containing CacheElement
deleteList.add(file);
iter.remove();
}
}
// remove empty cache elements
synchronized (cache) {
for (CacheElement elem : cache.values()) {
synchronized (elem) {
if (elem.list.size() == 0)
cache.remove(elem.hashKey);
}
}
}
}
// close all files in deleteList
for (CacheElement.CacheFile file : deleteList) {
if (force && file.isLocked.get())
cacheLog.warn("FileCache " + name + " force close locked file= " + file);
//counter.decrementAndGet();
try {
file.ncfile.setFileCache(null);
file.ncfile.close();
file.ncfile = null; // help the gc
} catch (IOException e) {
log.error("FileCache " + name + " close failed on " + file);
}
}
if (cacheLog.isDebugEnabled())
cacheLog.debug("*FileCache " + name + " clearCache force= " + force + " deleted= " + deleteList.size() + " left=" + files.size());
//System.out.println("\n*NetcdfFileCache.clearCache force= " + force + " deleted= " + deleteList.size() + " left=" + counter.get());
}
/**
* Show individual cache entries, add to formatter.
*
* @param format add to this
*/
@Override
public void showCache(Formatter format) {
ArrayList allFiles = new ArrayList<>(files.size());
for (CacheElement elem : cache.values()) {
synchronized (elem) {
allFiles.addAll(elem.list);
}
}
Collections.sort(allFiles); // sort so oldest are on top
format.format("%nFileCache %s (min=%d softLimit=%d hardLimit=%d scour=%d):%n", name, minElements, softLimit, hardLimit, period);
format.format(" isLocked accesses lastAccess location %n");
for (CacheElement.CacheFile file : allFiles) {
String loc = file.ncfile != null ? file.ncfile.getLocation() : "null";
format.format("%8s %9d %s == %s %n", file.isLocked, file.countAccessed, CalendarDateFormatter.toDateTimeStringISO(file.lastAccessed), loc);
}
showStats(format);
}
@Override
public List showCache() {
List allFiles = new ArrayList<>(files.size());
for (CacheElement elem : cache.values()) {
synchronized (elem) {
allFiles.addAll(elem.list);
}
}
Collections.sort(allFiles); // sort so oldest are on top
List result = new ArrayList<>(allFiles.size());
for (CacheElement.CacheFile file : allFiles) {
result.add(file.toString());
}
return result;
}
/**
* Add stat report (hits, misses, etc) to formatter.
*
* @param format add to this
*/
public void showStats(Formatter format) {
format.format(" hits= %d miss= %d nfiles= %d elems= %d%n", hits.get(), miss.get(), files.size(), cache.values().size());
}
public void showTracking(Formatter format) {
if (track == null) return;
List all = new ArrayList<>(track.size());
for (Tracker val : track.values()) all.add(val);
Collections.sort(all);
int seq = 0;
int countAll = 0;
int countHits = 0;
int countMiss = 0;
format.format("%nTracking All files in cache %s%n", name);
format.format(" # accum hit miss file%n");
for (Tracker t : all) {
seq++;
countAll += t.hit + t.miss;
countHits += t.hit;
countMiss += t.miss;
format.format("%6d %7d : %6d %6d %s%n", seq, countAll, t.hit, t.miss, t.key);
}
float r= ((float)countHits) / countAll;
format.format(" total=%7d : %6d %6d hit ratio=%f%n", countAll, countHits, countMiss, r);
}
@Override
public void resetTracking() {
track = new ConcurrentHashMap<>(5000);
trackAll = true;
}
private static class Tracker implements Comparable {
Object key;
int hit, miss;
private Tracker(Object key) {
this.key = key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tracker tracker = (Tracker) o;
if (!key.equals(tracker.key)) return false;
return true;
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public int compareTo(Tracker o) {
return Misc.compare(hit+miss, o.hit+o.miss);
}
}
/**
* Cleanup the cache, bringing it down to minimum number.
* Will close the LRU (least recently used) ones first. Will not close locked files.
* Normally this is done in a background thread, you dont need to call.
*
* We have to synchronize because of clearCache()
*/
synchronized void cleanup(int maxElements) {
try {
/* int size = counter.get();
int fsize = files.size();
if (debug && (size != fsize)) {
log.warn("FileCache " + name + " counter " + size + " doesnt match files().size=" + fsize);
} */
int size = files.size();
if (size <= minElements) return;
if (cacheLog.isDebugEnabled()) cacheLog.debug("FileCache {} cleanup started at {} for maxElements={}", name, CalendarDate.present(), maxElements);
if (debugCleanup)
System.out.printf(" FileCache %s cleanup started at %s for maxElements=%d%n", name, CalendarDate.present(), maxElements);
cleanups.incrementAndGet();
// add unlocked files to the all list
List allFiles = new ArrayList<>(size + 10);
for (CacheElement.CacheFile file : files.values()) {
if (!file.isLocked.get()) allFiles.add(file);
}
Collections.sort(allFiles); // sort so oldest are on top
// take oldest ones and put on delete list
int need2delete = size - minElements;
int minDelete = size - maxElements;
List deleteList = new ArrayList<>(need2delete);
int count = 0;
Iterator iter = allFiles.iterator();
while (iter.hasNext() && (count < need2delete)) {
CacheElement.CacheFile file = iter.next();
if (file.isLocked.compareAndSet(false, true)) { // lock it so it isnt used anywhere else
file.remove(); // remove from the containing element
deleteList.add(file);
count++;
}
}
if (count < minDelete) {
cacheLog.warn("FileCache " + name + " cleanup couldnt remove enough to keep under the maximum= " + maxElements + " due to locked files; currently at = " + (size - count));
if (debugCleanup)
System.out.println("FileCache " + name + " cleanup couldnt remove enough to keep under the maximum= " + maxElements + " due to locked files; currently at = " + (size - count));
}
// remove empty cache elements
synchronized (cache) {
for (CacheElement elem : cache.values()) {
synchronized (elem) {
if (elem.list.size() == 0)
cache.remove(elem.hashKey);
}
}
}
// now actually close the files
long start = System.currentTimeMillis();
for (CacheElement.CacheFile file : deleteList) {
//counter.decrementAndGet();
files.remove(file.ncfile);
try {
file.ncfile.setFileCache(null);
file.ncfile.close();
file.ncfile = null; // help the gc
} catch (IOException e) {
log.error("FileCache " + name + " close failed on " + file.getCacheName());
}
}
long took = System.currentTimeMillis() - start;
if (cacheLog.isDebugEnabled()) cacheLog.debug(" FileCache {} cleanup had={} removed={} took={} msecs%n", name, size,deleteList.size(), took);
if (debugCleanup)
System.out.printf(" FileCache %s cleanup had=%d removed=%d took=%d msecs%n", name, size,deleteList.size(), took);
} finally {
// allow scheduling again
hasScheduled.set(false);
}
}
// not private for testing
class CacheElement {
@GuardedBy("this")
final List list = new LinkedList<>(); // may have multiple copies of the same file opened
final Object hashKey;
CacheElement(FileCacheable ncfile, Object hashKey) {
this.hashKey = hashKey;
CacheFile file = new CacheFile(ncfile);
list.add(file);
if (debug) {
if (files.get(ncfile) != null)
cacheLog.error("files already has " + hashKey + " " + name);
}
files.put(ncfile, file);
if (cacheLog.isDebugEnabled()) cacheLog.debug("CacheElement add to cache " + hashKey + " " + name);
}
CacheFile addFile(FileCacheable ncfile) {
CacheFile file = new CacheFile(ncfile);
synchronized (this) {
list.add(file);
}
if (debug) {
if (files.get(ncfile) != null)
cacheLog.error("files (2) already has " + hashKey + " " + name);
}
files.put(ncfile, file);
return file;
}
public String toString() {
return hashKey + " count=" + list.size();
}
class CacheFile implements Comparable {
FileCacheable ncfile; // actually final, but we null it out for gc
final AtomicBoolean isLocked = new AtomicBoolean(true);
int countAccessed = 0;
long lastModified = 0;
long lastAccessed = 0;
private CacheFile(FileCacheable ncfile) {
this.ncfile = ncfile;
this.lastModified = ncfile.getLastModified();
this.lastAccessed = System.currentTimeMillis();
ncfile.setFileCache(FileCache.this);
if (cacheLog.isDebugEnabled()) cacheLog.debug("FileCache " + name + " add to cache " + hashKey);
if (debugPrint) System.out.println(" FileCache " + name + " add to cache " + hashKey);
}
String getCacheName() {
return ncfile.getLocation();
}
void remove() {
synchronized (CacheElement.this) {
if (!list.remove(this))
cacheLog.warn("FileCache " + name + " could not remove " + ncfile.getLocation());
}
if (cacheLog.isDebugEnabled()) cacheLog.debug("FileCache " + name + " remove " + ncfile.getLocation());
if (debugPrint) System.out.println(" FileCache " + name + " remove " + ncfile.getLocation());
}
public String toString() {
return isLocked + " " + countAccessed + " " + CalendarDateFormatter.toDateTimeStringISO(lastAccessed) + " " + ncfile.getLocation();
}
public int compareTo(CacheFile o) {
return (int) (lastAccessed - o.lastAccessed);
}
}
}
private class CleanupTask implements Runnable {
public void run() {
if (disabled.get()) return;
cleanup(softLimit);
}
}
}