// // CacheStrategy.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. 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; either version 2 of the License, or (at your option) any later version. 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 */ package loci.formats.cache; import java.util.Arrays; import java.util.Comparator; import java.util.Vector; import loci.formats.FormatTools; /** * Superclass of cache strategies. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/cache/CacheStrategy.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/cache/CacheStrategy.java;hb=HEAD">Gitweb</a></dd></dl> */ public abstract class CacheStrategy implements CacheReporter, Comparator, ICacheStrategy { // -- Constants -- /** Default cache range. */ public static final int DEFAULT_RANGE = 0; // -- Fields -- /** Length of each dimensional axis. */ protected int[] lengths; /** The order in which planes should be loaded along each axis. */ protected int[] order; /** Number of planes to cache along each axis. */ protected int[] range; /** Priority for caching each axis. Controls axis caching order. */ protected int[] priorities; /** * The list of dimensional positions to consider caching, in order of * preference based on strategy, axis priority and planar ordering. */ private int[][] positions; /** * Whether load order array needs to be recomputed * before building the next load list. */ private boolean dirty; /** List of cache event listeners. */ protected Vector listeners; // -- Constructors -- /** Constructs a cache strategy. */ public CacheStrategy(int[] lengths) { this.lengths = lengths; order = new int[lengths.length]; Arrays.fill(order, CENTERED_ORDER); range = new int[lengths.length]; Arrays.fill(range, DEFAULT_RANGE); priorities = new int[lengths.length]; Arrays.fill(priorities, NORMAL_PRIORITY); positions = getPossiblePositions(); dirty = true; listeners = new Vector(); } // -- Abstract CacheStrategy API methods -- /** * Gets positions to consider for possible inclusion in the cache, * assuming a current position at the origin (0). */ protected abstract int[][] getPossiblePositions(); // -- CacheStrategy API methods -- /** * Computes the distance from the given axis value to the * axis center, taking into account the axis ordering scheme. */ public int distance(int axis, int value) { switch (order[axis]) { case CENTERED_ORDER: if (value == 0) return 0; int vb = lengths[axis] - value; return value <= vb ? value : vb; case FORWARD_ORDER: return value; case BACKWARD_ORDER: if (value == 0) return 0; return lengths[axis] - value; default: throw new IllegalStateException("unknown order: " + order[axis]); } } // -- Internal CacheStrategy API methods -- /** Shortcut for converting N-D position to rasterized position. */ protected int raster(int[] pos) { return FormatTools.positionToRaster(lengths, pos); } /** Shortcut for converting rasterized position to N-D position. */ protected int[] pos(int raster) { return FormatTools.rasterToPosition(lengths, raster); } /** * Shortcut for converting rasterized position to N-D position, * using the given array instead of allocating a new one. */ protected int[] pos(int raster, int[] pos) { return FormatTools.rasterToPosition(lengths, raster, pos); } /** Shortcut for computing total number of positions. */ protected int length() { return FormatTools.getRasterLength(lengths); } // -- CacheReporter API methods -- /* @see CacheReporter#addCacheListener(CacheListener) */ public void addCacheListener(CacheListener l) { synchronized (listeners) { listeners.add(l); } } /* @see CacheReporter#removeCacheListener(CacheListener) */ public void removeCacheListener(CacheListener l) { synchronized (listeners) { listeners.remove(l); } } /* @see CacheReporter#getCacheListeners() */ public CacheListener[] getCacheListeners() { CacheListener[] l; synchronized (listeners) { l = new CacheListener[listeners.size()]; listeners.copyInto(l); } return l; } // -- Comparator API methods -- /** * Default comparator orders dimensional positions based on distance from the * current position, taking into account axis priorities and planar ordering. */ public int compare(Object o1, Object o2) { int[] p1 = (int[]) o1; int[] p2 = (int[]) o2; // compare sum of axis distances for each priority for (int p=MAX_PRIORITY; p>=MIN_PRIORITY; p--) { int dist1 = 0, dist2 = 0; for (int i=0; i<p1.length; i++) { if (priorities[i] == p) { dist1 += distance(i, p1[i]); dist2 += distance(i, p2[i]); } } int diff = dist1 - dist2; if (diff != 0) return diff; } // compare number of diverging axes for each priority for (int p=MAX_PRIORITY; p>=MIN_PRIORITY; p--) { int div1 = 0, div2 = 0; for (int i=0; i<p1.length; i++) { if (priorities[i] == p) { if (p1[i] != 0) div1++; if (p2[i] != 0) div2++; } } int diff = div1 - div2; if (diff != 0) return diff; } return 0; } // -- ICacheStrategy API methods -- /* @see ICacheStrategy#getLoadList(int[]) */ public int[][] getLoadList(int[] pos) throws CacheException { int[][] loadList = null; synchronized (positions) { if (dirty) { // reorder the positions list Arrays.sort(positions, this); dirty = false; } // count up number of load list entries int c = 0; for (int i=0; i<positions.length; i++) { int[] ipos = positions[i]; // verify position is close enough to current position boolean ok = true; for (int j=0; j<ipos.length; j++) { if (distance(j, ipos[j]) > range[j]) { ok = false; break; } } if (ok) c++; // in range } loadList = new int[c][lengths.length]; c = 0; // build load list for (int i=0; i<positions.length; i++) { int[] ipos = positions[i]; // verify position is close enough to current position boolean ok = true; for (int j=0; j<ipos.length && c<loadList.length; j++) { if (distance(j, ipos[j]) > range[j]) { ok = false; break; } int value = (pos[j] + ipos[j]) % lengths[j]; // normalize axis value loadList[c][j] = value; } if (ok) c++; // in range; lock in load list entry } } return loadList; } /* @see ICacheStrategy#getPriorities() */ public int[] getPriorities() { return priorities; } /* @see ICacheStrategy#setPriority() */ public void setPriority(int priority, int axis) { if (priority < MIN_PRIORITY || priority > MAX_PRIORITY) { throw new IllegalArgumentException( "Invalid priority for axis #" + axis + ": " + priority); } synchronized (positions) { priorities[axis] = priority; dirty = true; } notifyListeners(new CacheEvent(this, CacheEvent.PRIORITIES_CHANGED)); } /* @see ICacheStrategy#getOrder() */ public int[] getOrder() { return order; } /* @see ICacheStrategy#getOrder() */ public void setOrder(int order, int axis) { if (order != CENTERED_ORDER && order != FORWARD_ORDER && order != BACKWARD_ORDER) { throw new IllegalArgumentException( "Invalid order for axis #" + axis + ": " + order); } synchronized (positions) { this.order[axis] = order; dirty = true; } notifyListeners(new CacheEvent(this, CacheEvent.ORDER_CHANGED)); } /* @see ICacheStrategy#getRange() */ public int[] getRange() { return range; } /* @see ICacheStrategy#setRange(int, int) */ public void setRange(int num, int axis) { if (num < 0) { throw new IllegalArgumentException( "Invalid range for axis #" + axis + ": " + num); } range[axis] = num; notifyListeners(new CacheEvent(this, CacheEvent.RANGE_CHANGED)); } /* @see ICacheStrategy#getLengths() */ public int[] getLengths() { return lengths; } // -- 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 = (CacheListener) listeners.elementAt(i); l.cacheUpdated(e); } } } }