/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package loci.formats.cache;
import java.util.Vector;
import loci.formats.FormatTools;
/**
* Cache provides a means of managing subsets of large collections of image
* planes in memory. Each cache has a source, which provides image planes or
* other objects from somewhere (typically derived from an IFormatReader),
* and a strategy dictating which image planes should be loaded into the cache,
* in which order, and which planes should be dropped from the cache. The
* essence of the logic is the idea that the cache has a "current" position
* across the multidimensional image series's dimensional axes, with the
* strategy indicating which surrounding planes to load into the cache (i.e.,
* planes within a certain range along each dimensional axis).
*/
public class Cache implements CacheReporter {
// -- Fields --
/** Current cache strategy. */
protected ICacheStrategy strategy;
/** Current cache source. */
protected ICacheSource source;
/** Current dimensional position. */
protected int[] currentPos;
/** Master array containing cached objects. */
protected Object[] cache;
/** Whether each position is currently supposed to be cached. */
protected boolean[] inCache;
/** List of cache event listeners. */
protected Vector<CacheListener> listeners;
/** Whether the cache should automatically update when a parameter changes. */
protected boolean autoUpdate;
// -- Constructors --
/** Constructs an object cache with the given cache strategy and source. */
public Cache(ICacheStrategy strategy, ICacheSource source,
boolean autoUpdate) throws CacheException
{
if (strategy == null) throw new CacheException("strategy is null");
if (source == null) throw new CacheException("source is null");
this.strategy = strategy;
this.source = source;
this.autoUpdate = autoUpdate;
listeners = new Vector<CacheListener>();
reset();
if (autoUpdate) recache();
}
// -- Cache API methods --
/** Gets the cached object at the given dimensional position. */
public Object getObject(int[] pos) throws CacheException {
if (pos.length != strategy.getLengths().length) {
throw new CacheException("Invalid number of axes; got " + pos.length +
"; expected " + strategy.getLengths().length);
}
int ndx = FormatTools.positionToRaster(strategy.getLengths(), pos);
return cache[ndx];
}
/**
* Returns true if the object at the given dimensional position is
* in the cache.
*/
public boolean isInCache(int[] pos) throws CacheException {
return isInCache(FormatTools.positionToRaster(strategy.getLengths(), pos));
}
/** Returns true if the object at the given index is in the cache. */
public boolean isInCache(int pos) throws CacheException {
return inCache[pos];
}
/** Reallocates the cache. */
public void reset() throws CacheException {
currentPos = new int[strategy.getLengths().length];
cache = new Object[source.getObjectCount()];
inCache = new boolean[source.getObjectCount()];
}
/** Gets the cache's caching strategy. */
public ICacheStrategy getStrategy() { return strategy; }
/** Gets the cache's caching source. */
public ICacheSource getSource() { return source; }
/** Gets the current dimensional position. */
public int[] getCurrentPos() { return currentPos; }
/** Sets the cache's caching strategy. */
public void setStrategy(ICacheStrategy strategy) throws CacheException {
if (strategy == null) throw new CacheException("strategy is null");
synchronized (listeners) {
for (int i=0; i<listeners.size(); i++) {
CacheListener l = listeners.elementAt(i);
this.strategy.removeCacheListener(l);
strategy.addCacheListener(l);
}
}
this.strategy = strategy;
notifyListeners(new CacheEvent(this, CacheEvent.STRATEGY_CHANGED));
reset();
if (autoUpdate) recache();
}
/** Sets the cache's caching source. */
public void setSource(ICacheSource source) throws CacheException {
if (source == null) throw new CacheException("source is null");
this.source = source;
notifyListeners(new CacheEvent(this, CacheEvent.SOURCE_CHANGED));
reset();
if (autoUpdate) recache();
}
/** Sets the current dimensional position. */
public void setCurrentPos(int[] pos) throws CacheException {
if (pos == null) throw new CacheException("pos is null");
if (pos.length != currentPos.length) {
throw new CacheException("pos length mismatch (is " +
pos.length + ", expected " + currentPos.length + ")");
}
int[] len = strategy.getLengths();
for (int i=0; i<pos.length; i++) {
if (pos[i] < 0 || pos[i] >= len[i]) {
throw new CacheException("invalid pos[" + i + "] (is " +
pos[i] + ", expected [0, " + (len[i] - 1) + "])");
}
}
System.arraycopy(pos, 0, currentPos, 0, pos.length);
int ndx = FormatTools.positionToRaster(len, pos);
notifyListeners(new CacheEvent(this, CacheEvent.POSITION_CHANGED, ndx));
if (autoUpdate) recache();
}
/** Updates the given plane. */
public void recache(int n) throws CacheException {
int[][] indices = strategy.getLoadList(currentPos);
int[] len = strategy.getLengths();
for (int i=0; i<inCache.length; i++) {
boolean found = false;
for (int j=0; j<indices.length; j++) {
if (i == FormatTools.positionToRaster(len, indices[j])) {
found = true;
break;
}
}
if (!found) {
inCache[i] = false;
if (cache[i] != null) {
cache[i] = null;
notifyListeners(new CacheEvent(this, CacheEvent.OBJECT_DROPPED, i));
}
}
}
int ndx = FormatTools.positionToRaster(len, indices[n]);
if (ndx >= 0) inCache[ndx] = true;
if (cache[ndx] == null) {
cache[ndx] = source.getObject(ndx);
notifyListeners(new CacheEvent(this, CacheEvent.OBJECT_LOADED, ndx));
}
}
/** Updates all planes on the load list. */
public void recache() throws CacheException {
// what happens if cache source and cache strategy lengths do not match?
// throw exception in that case
// what if developer wants to change both source and strategy to something
// completely different -- make sure it works
// in general, what if developer wants to tweak a few parameters before
// starting to reload things? probably should have a recache method that
// you must explicitly call to trigger the separate thread refresh
// need to be careful -- don't want cache parameter changes affecting the
// recaching thread on the fly -- should refresh those parameter values
// each time through the loop only (i.e., only when a recache call occurs)
//
// /lo
for (int i=0; i<strategy.getLoadList(currentPos).length; i++) {
recache(i);
}
}
// -- CacheReporter API methods --
/* @see CacheReporter#addCacheListener(CacheListener) */
@Override
public void addCacheListener(CacheListener l) {
synchronized (listeners) {
listeners.add(l);
strategy.addCacheListener(l);
}
}
/* @see CacheReporter#removeCacheListener(CacheListener) */
@Override
public void removeCacheListener(CacheListener l) {
synchronized (listeners) {
listeners.remove(l);
strategy.removeCacheListener(l);
}
}
/* @see CacheReporter#getCacheListeners() */
@Override
public CacheListener[] getCacheListeners() {
CacheListener[] l;
synchronized (listeners) {
l = new CacheListener[listeners.size()];
listeners.copyInto(l);
}
return l;
}
// -- Helper methods --
/** Informs listeners of a cache update. */
protected void notifyListeners(CacheEvent e) {
synchronized (listeners) {
for (int i=0; i<listeners.size(); i++) {
CacheListener l = listeners.elementAt(i);
l.cacheUpdated(e);
}
}
}
}