0001: /*
0002: * $RCSfile: SunTileCache.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.2 $
0009: * $Date: 2005/11/15 01:50:59 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.util;
0013:
0014: import java.awt.RenderingHints;
0015: import java.util.Collections;
0016: import java.util.Comparator;
0017: import java.util.ConcurrentModificationException;
0018: import java.util.Iterator;
0019: import java.util.Observable;
0020: import java.util.Vector;
0021: import java.util.Enumeration;
0022: import java.util.Hashtable;
0023: import java.util.SortedSet;
0024: import java.util.TreeSet;
0025: import java.awt.Point;
0026: import java.awt.image.Raster;
0027: import java.awt.image.RenderedImage;
0028: import javax.media.jai.EnumeratedParameter;
0029: import javax.media.jai.TileCache;
0030: import javax.media.jai.util.ImagingListener;
0031:
0032: /**
0033: * This is Sun Microsystems' reference implementation of the
0034: * <code>javax.media.jai.TileCache</code> interface. It provides a
0035: * central location for images to cache computed tiles, and is used as
0036: * the default tile cache mechanism when no other tile cache objects
0037: * are specified.
0038: *
0039: * <p> In this implementation, the cache size is limited by the memory
0040: * capacity, which may be set at construction time or using the
0041: * <code>setMemoryCapacity(long)</code> method. The tile capacity
0042: * is not used. Different images may have very different tile sizes.
0043: * Therefore, the memory usage for a specific tile capacity may vary
0044: * greatly depends on the type of images involved. In fact, the tile
0045: * capacity is rather meaningless.
0046: *
0047: * @see javax.media.jai.TileCache
0048: *
0049: */
0050:
0051: //
0052: // NOTE: code is inlined for performance reasons
0053: //
0054: public final class SunTileCache extends Observable implements
0055: TileCache, CacheDiagnostics {
0056:
0057: /** The default memory capacity of the cache (16 MB). */
0058: private static final long DEFAULT_MEMORY_CAPACITY = 16L * 1024L * 1024L;
0059:
0060: /** The default hashtable capacity (heuristic) */
0061: private static final int DEFAULT_HASHTABLE_CAPACITY = 1009; // prime number
0062:
0063: /** The hashtable load factor */
0064: private static final float LOAD_FACTOR = 0.5F;
0065:
0066: /**
0067: * The tile cache.
0068: * A Hashtable is used to cache the tiles. The "key" is a
0069: * <code>Object</code> determined based on tile owner's UID if any or
0070: * hashCode if the UID doesn't exist, and tile index. The
0071: * "value" is a SunCachedTile.
0072: */
0073: private Hashtable cache;
0074:
0075: /**
0076: * Sorted (Tree) Set used with tile metrics.
0077: * Adds another level of metrics used to determine
0078: * which tiles are removed during memoryControl().
0079: */
0080: private SortedSet cacheSortedSet;
0081:
0082: /** The memory capacity of the cache. */
0083: private long memoryCapacity;
0084:
0085: /** The amount of memory currently being used by the cache. */
0086: private long memoryUsage = 0;
0087:
0088: /** The amount of memory to keep after memory control */
0089: private float memoryThreshold = 0.75F;
0090:
0091: /** A indicator for tile access time. */
0092: private long timeStamp = 0;
0093:
0094: /** Custom comparator used to determine tile cost or
0095: * priority ordering in the tile cache.
0096: */
0097: private Comparator comparator = null;
0098:
0099: /** Pointer to the first (newest) tile of the linked SunCachedTile list. */
0100: private SunCachedTile first = null;
0101:
0102: /** Pointer to the last (oldest) tile of the linked SunCachedTile list. */
0103: private SunCachedTile last = null;
0104:
0105: /** Tile count used for diagnostics */
0106: private long tileCount = 0;
0107:
0108: /** Cache hit count */
0109: private long hitCount = 0;
0110:
0111: /** Cache miss count */
0112: private long missCount = 0;
0113:
0114: /** Diagnostics enable/disable */
0115: private boolean diagnostics = false;
0116:
0117: // diagnostic actions
0118: // !!! If actions are changed in any way (removal, modification, addition)
0119: // then the getCachedTileActions() method below should be changed to match.
0120: private static final int ADD = 0;
0121: private static final int REMOVE = 1;
0122: private static final int REMOVE_FROM_FLUSH = 2;
0123: private static final int REMOVE_FROM_MEMCON = 3;
0124: private static final int UPDATE_FROM_ADD = 4;
0125: private static final int UPDATE_FROM_GETTILE = 5;
0126: private static final int ABOUT_TO_REMOVE = 6;
0127:
0128: /**
0129: * Returns an array of <code>EnumeratedParameter</code>s corresponding
0130: * to the numeric values returned by the <code>getAction()</code>
0131: * method of the <code>CachedTile</code> implementation used by
0132: * <code>SunTileCache</code>. The "name" of each
0133: * <code>EnumeratedParameter</code> provides a brief string
0134: * describing the numeric action value.
0135: */
0136: public static EnumeratedParameter[] getCachedTileActions() {
0137: return new EnumeratedParameter[] {
0138: new EnumeratedParameter("add", ADD),
0139: new EnumeratedParameter("remove", REMOVE),
0140: new EnumeratedParameter("remove_by_flush",
0141: REMOVE_FROM_FLUSH),
0142: new EnumeratedParameter("remove_by_memorycontrol",
0143: REMOVE_FROM_MEMCON),
0144: new EnumeratedParameter("timestamp_update_by_add",
0145: UPDATE_FROM_ADD),
0146: new EnumeratedParameter("timestamp_update_by_gettile",
0147: UPDATE_FROM_GETTILE),
0148: new EnumeratedParameter("preremove", ABOUT_TO_REMOVE) };
0149: }
0150:
0151: /**
0152: * No args constructor. Use the DEFAULT_MEMORY_CAPACITY of 16 Megs.
0153: */
0154: public SunTileCache() {
0155: this (DEFAULT_MEMORY_CAPACITY);
0156: }
0157:
0158: /**
0159: * Constructor. The memory capacity should be explicitly specified.
0160: *
0161: * @param memoryCapacity The maximum cache memory size in bytes.
0162: *
0163: * @throws IllegalArgumentException If <code>memoryCapacity</code>
0164: * is less than 0.
0165: */
0166: public SunTileCache(long memoryCapacity) {
0167: if (memoryCapacity < 0) {
0168: throw new IllegalArgumentException(JaiI18N
0169: .getString("SunTileCache"));
0170: }
0171:
0172: this .memoryCapacity = memoryCapacity;
0173:
0174: // try to get a prime number (more efficient?)
0175: // lower values of LOAD_FACTOR increase speed, decrease space efficiency
0176: cache = new Hashtable(DEFAULT_HASHTABLE_CAPACITY, LOAD_FACTOR);
0177: }
0178:
0179: /**
0180: * Adds a tile to the cache.
0181: *
0182: * <p> If the specified tile is already in the cache, it will not be
0183: * cached again. If by adding this tile, the cache exceeds the memory
0184: * capacity, older tiles in the cache are removed to keep the cache
0185: * memory usage under the specified limit.
0186: *
0187: * @param owner The image the tile blongs to.
0188: * @param tileX The tile's X index within the image.
0189: * @param tileY The tile's Y index within the image.
0190: * @param tile The tile to be cached.
0191: */
0192: public void add(RenderedImage owner, int tileX, int tileY,
0193: Raster tile) {
0194: add(owner, tileX, tileY, tile, null);
0195: }
0196:
0197: /**
0198: * Adds a tile to the cache with an associated tile compute cost.
0199: *
0200: * <p> If the specified tile is already in the cache, it will not be
0201: * cached again. If by adding this tile, the cache exceeds the memory
0202: * capacity, older tiles in the cache are removed to keep the cache
0203: * memory usage under the specified limit.
0204: *
0205: * @param owner The image the tile blongs to.
0206: * @param tileX The tile's X index within the image.
0207: * @param tileY The tile's Y index within the image.
0208: * @param tile The tile to be cached.
0209: * @param tileCacheMetric Metric for prioritizing tiles
0210: */
0211: public synchronized void add(RenderedImage owner, int tileX,
0212: int tileY, Raster tile, Object tileCacheMetric) {
0213:
0214: if (memoryCapacity == 0) {
0215: return;
0216: }
0217:
0218: // This tile is not in the cache; create a new SunCachedTile.
0219: // else just update. (code inlined for performance).
0220: Object key = SunCachedTile.hashKey(owner, tileX, tileY);
0221: SunCachedTile ct = (SunCachedTile) cache.get(key);
0222:
0223: if (ct != null) {
0224: // tile is cached, inlines update()
0225: ct.timeStamp = timeStamp++;
0226:
0227: if (ct != first) {
0228: // Bring this tile to the beginning of the list.
0229: if (ct == last) {
0230: last = ct.previous;
0231: last.next = null;
0232: } else {
0233: ct.previous.next = ct.next;
0234: ct.next.previous = ct.previous;
0235: }
0236:
0237: ct.previous = null;
0238: ct.next = first;
0239:
0240: first.previous = ct;
0241: first = ct;
0242: }
0243:
0244: hitCount++;
0245:
0246: if (diagnostics) {
0247: ct.action = UPDATE_FROM_ADD;
0248: setChanged();
0249: notifyObservers(ct);
0250: }
0251: } else {
0252: // create a new tile
0253: ct = new SunCachedTile(owner, tileX, tileY, tile,
0254: tileCacheMetric);
0255:
0256: // Don't cache tile if adding it would provoke memoryControl()
0257: // which would in turn only end up removing the tile.
0258: if (memoryUsage + ct.memorySize > memoryCapacity
0259: && ct.memorySize > (long) (memoryCapacity * memoryThreshold)) {
0260: return;
0261: }
0262:
0263: ct.timeStamp = timeStamp++;
0264: ct.previous = null;
0265: ct.next = first;
0266:
0267: if (first == null && last == null) {
0268: first = ct;
0269: last = ct;
0270: } else {
0271: first.previous = ct;
0272: first = ct; // put this tile at the top of the list
0273: }
0274:
0275: // add to tile cache
0276: if (cache.put(ct.key, ct) == null) {
0277: memoryUsage += ct.memorySize;
0278: tileCount++;
0279: //missCount++; Not necessary?
0280:
0281: if (cacheSortedSet != null) {
0282: cacheSortedSet.add(ct);
0283: }
0284:
0285: if (diagnostics) {
0286: ct.action = ADD;
0287: setChanged();
0288: notifyObservers(ct);
0289: }
0290: }
0291:
0292: // Bring memory usage down to memoryThreshold % of memory capacity.
0293: if (memoryUsage > memoryCapacity) {
0294: memoryControl();
0295: }
0296: }
0297: }
0298:
0299: /**
0300: * Removes a tile from the cache.
0301: *
0302: * <p> If the specified tile is not in the cache, this method
0303: * does nothing.
0304: */
0305: public synchronized void remove(RenderedImage owner, int tileX,
0306: int tileY) {
0307:
0308: if (memoryCapacity == 0) {
0309: return;
0310: }
0311:
0312: Object key = SunCachedTile.hashKey(owner, tileX, tileY);
0313: SunCachedTile ct = (SunCachedTile) cache.get(key);
0314:
0315: if (ct != null) {
0316: // Notify observers that a tile is about to be removed.
0317: // It is possible that the tile will be removed from the
0318: // cache before the observers get notified. This should
0319: // be ok, since a hard reference to the tile will be
0320: // kept for the observers, so the garbage collector won't
0321: // remove the tile until the observers release it.
0322: ct.action = ABOUT_TO_REMOVE;
0323: setChanged();
0324: notifyObservers(ct);
0325:
0326: ct = (SunCachedTile) cache.remove(key);
0327:
0328: // recalculate memoryUsage only if tile is actually removed
0329: if (ct != null) {
0330: memoryUsage -= ct.memorySize;
0331: tileCount--;
0332:
0333: if (cacheSortedSet != null) {
0334: cacheSortedSet.remove(ct);
0335: }
0336:
0337: if (ct == first) {
0338: if (ct == last) {
0339: first = null; // only one tile in the list
0340: last = null;
0341: } else {
0342: first = ct.next;
0343: first.previous = null;
0344: }
0345: } else if (ct == last) {
0346: last = ct.previous;
0347: last.next = null;
0348: } else {
0349: ct.previous.next = ct.next;
0350: ct.next.previous = ct.previous;
0351: }
0352:
0353: // Notify observers that a tile has been removed.
0354: // If the core's hard references go away, the
0355: // soft references will be garbage collected.
0356: // Usually, by the time the observers are notified
0357: // the ct owner and tile are nulled by the GC, so
0358: // we can't really tell which op was removed
0359: // This occurs when OpImage's finalize method is
0360: // invoked. This code works ok when remove is
0361: // called directly. (by flush() for example).
0362: // If the soft references are GC'd, the timeStamp
0363: // will no longer be contiguous, it will be
0364: // unique, so this is ok.
0365: if (diagnostics) {
0366: ct.action = REMOVE;
0367: setChanged();
0368: notifyObservers(ct);
0369: }
0370:
0371: ct.previous = null;
0372: ct.next = null;
0373: ct = null;
0374: }
0375: }
0376: }
0377:
0378: /**
0379: * Retrieves a tile from the cache.
0380: *
0381: * <p> If the specified tile is not in the cache, this method
0382: * returns <code>null</code>. If the specified tile is in the
0383: * cache, its last-access time is updated.
0384: *
0385: * @param owner The image the tile blongs to.
0386: * @param tileX The tile's X index within the image.
0387: * @param tileY The tile's Y index within the image.
0388: */
0389: public synchronized Raster getTile(RenderedImage owner, int tileX,
0390: int tileY) {
0391:
0392: Raster tile = null;
0393:
0394: if (memoryCapacity == 0) {
0395: return null;
0396: }
0397:
0398: Object key = SunCachedTile.hashKey(owner, tileX, tileY);
0399: SunCachedTile ct = (SunCachedTile) cache.get(key);
0400:
0401: if (ct == null) {
0402: missCount++;
0403: } else { // found tile in cache
0404: tile = (Raster) ct.getTile();
0405:
0406: // Update last-access time. (update() inlined for performance)
0407: ct.timeStamp = timeStamp++;
0408:
0409: if (ct != first) {
0410: // Bring this tile to the beginning of the list.
0411: if (ct == last) {
0412: last = ct.previous;
0413: last.next = null;
0414: } else {
0415: ct.previous.next = ct.next;
0416: ct.next.previous = ct.previous;
0417: }
0418:
0419: ct.previous = null;
0420: ct.next = first;
0421:
0422: first.previous = ct;
0423: first = ct;
0424: }
0425:
0426: hitCount++;
0427:
0428: if (diagnostics) {
0429: ct.action = UPDATE_FROM_GETTILE;
0430: setChanged();
0431: notifyObservers(ct);
0432: }
0433: }
0434:
0435: return tile;
0436: }
0437:
0438: /**
0439: * Retrieves a contiguous array of all tiles in the cache which are
0440: * owned by the specified image. May be <code>null</code> if there
0441: * were no tiles in the cache. The array contains no null entries.
0442: *
0443: * @param owner The <code>RenderedImage</code> to which the tiles belong.
0444: * @return An array of all tiles owned by the specified image or
0445: * <code>null</code> if there are none currently in the cache.
0446: */
0447: public synchronized Raster[] getTiles(RenderedImage owner) {
0448: Raster[] tiles = null;
0449:
0450: if (memoryCapacity == 0) {
0451: return null;
0452: }
0453:
0454: int size = Math.min(
0455: owner.getNumXTiles() * owner.getNumYTiles(),
0456: (int) tileCount);
0457:
0458: if (size > 0) {
0459: int minTx = owner.getMinTileX();
0460: int minTy = owner.getMinTileY();
0461: int maxTx = minTx + owner.getNumXTiles();
0462: int maxTy = minTy + owner.getNumYTiles();
0463:
0464: // arbitrarily set a temporary vector size
0465: Vector temp = new Vector(10, 20);
0466:
0467: for (int y = minTy; y < maxTy; y++) {
0468: for (int x = minTx; x < maxTx; x++) {
0469: // inline this method
0470: //Raster raster = getTile(owner, x, y);
0471: //************************
0472: Raster raster = null;
0473: Object key = SunCachedTile.hashKey(owner, x, y);
0474: SunCachedTile ct = (SunCachedTile) cache.get(key);
0475:
0476: if (ct == null) {
0477: raster = null;
0478: missCount++;
0479: } else { // found tile in cache
0480: raster = (Raster) ct.getTile();
0481:
0482: // Update last-access time. (update() inlined for performance)
0483: ct.timeStamp = timeStamp++;
0484:
0485: if (ct != first) {
0486: // Bring this tile to the beginning of the list.
0487: if (ct == last) {
0488: last = ct.previous;
0489: last.next = null;
0490: } else {
0491: ct.previous.next = ct.next;
0492: ct.next.previous = ct.previous;
0493: }
0494:
0495: ct.previous = null;
0496: ct.next = first;
0497:
0498: first.previous = ct;
0499: first = ct;
0500: }
0501:
0502: hitCount++;
0503:
0504: if (diagnostics) {
0505: ct.action = UPDATE_FROM_GETTILE;
0506: setChanged();
0507: notifyObservers(ct);
0508: }
0509: }
0510: //************************
0511:
0512: if (raster != null) {
0513: temp.add(raster);
0514: }
0515: }
0516: }
0517:
0518: int tmpsize = temp.size();
0519: if (tmpsize > 0) {
0520: tiles = (Raster[]) temp.toArray(new Raster[tmpsize]);
0521: }
0522: }
0523:
0524: return tiles;
0525: }
0526:
0527: /**
0528: * Removes all the tiles that belong to a <code>RenderedImage</code>
0529: * from the cache.
0530: *
0531: * @param owner The image whose tiles are to be removed from the cache.
0532: */
0533: public void removeTiles(RenderedImage owner) {
0534: if (memoryCapacity > 0) {
0535: int minTx = owner.getMinTileX();
0536: int minTy = owner.getMinTileY();
0537: int maxTx = minTx + owner.getNumXTiles();
0538: int maxTy = minTy + owner.getNumYTiles();
0539:
0540: for (int y = minTy; y < maxTy; y++) {
0541: for (int x = minTx; x < maxTx; x++) {
0542: remove(owner, x, y);
0543: }
0544: }
0545: }
0546: }
0547:
0548: /**
0549: * Adds an array of tiles to the tile cache.
0550: *
0551: * @param owner The <code>RenderedImage</code> that the tile belongs to.
0552: * @param tileIndices An array of <code>Point</code>s containing the
0553: * <code>tileX</code> and <code>tileY</code> indices for each tile.
0554: * @param tiles The array of tile <code>Raster</code>s containing tile data.
0555: * @param tileCacheMetric Object which provides an ordering metric
0556: * associated with the <code>RenderedImage</code> owner.
0557: * @since 1.1
0558: */
0559: public synchronized void addTiles(RenderedImage owner,
0560: Point[] tileIndices, Raster[] tiles, Object tileCacheMetric) {
0561:
0562: if (memoryCapacity == 0) {
0563: return;
0564: }
0565:
0566: // this just inlines the add routine (no sync overhead for each call).
0567: for (int i = 0; i < tileIndices.length; i++) {
0568: int tileX = tileIndices[i].x;
0569: int tileY = tileIndices[i].y;
0570: Raster tile = tiles[i];
0571:
0572: Object key = SunCachedTile.hashKey(owner, tileX, tileY);
0573: SunCachedTile ct = (SunCachedTile) cache.get(key);
0574:
0575: if (ct != null) {
0576: // tile is cached, inlines update()
0577: ct.timeStamp = timeStamp++;
0578:
0579: if (ct != first) {
0580: // Bring this tile to the beginning of the list.
0581: if (ct == last) {
0582: last = ct.previous;
0583: last.next = null;
0584: } else {
0585: ct.previous.next = ct.next;
0586: ct.next.previous = ct.previous;
0587: }
0588:
0589: ct.previous = null;
0590: ct.next = first;
0591:
0592: first.previous = ct;
0593: first = ct;
0594: }
0595:
0596: hitCount++;
0597:
0598: if (diagnostics) {
0599: ct.action = UPDATE_FROM_ADD;
0600: setChanged();
0601: notifyObservers(ct);
0602: }
0603: } else {
0604: // create a new tile
0605: ct = new SunCachedTile(owner, tileX, tileY, tile,
0606: tileCacheMetric);
0607:
0608: // Don't cache tile if adding it would provoke memoryControl()
0609: // which would in turn only end up removing the tile.
0610: if (memoryUsage + ct.memorySize > memoryCapacity
0611: && ct.memorySize > (long) (memoryCapacity * memoryThreshold)) {
0612: return;
0613: }
0614:
0615: ct.timeStamp = timeStamp++;
0616: ct.previous = null;
0617: ct.next = first;
0618:
0619: if (first == null && last == null) {
0620: first = ct;
0621: last = ct;
0622: } else {
0623: first.previous = ct;
0624: first = ct; // put this tile at the top of the list
0625: }
0626:
0627: // add to tile cache
0628: if (cache.put(ct.key, ct) == null) {
0629: memoryUsage += ct.memorySize;
0630: tileCount++;
0631: //missCount++; Not necessary?
0632:
0633: if (cacheSortedSet != null) {
0634: cacheSortedSet.add(ct);
0635: }
0636:
0637: if (diagnostics) {
0638: ct.action = ADD;
0639: setChanged();
0640: notifyObservers(ct);
0641: }
0642: }
0643:
0644: // Bring memory usage down to memoryThreshold % of memory capacity.
0645: if (memoryUsage > memoryCapacity) {
0646: memoryControl();
0647: }
0648: }
0649: }
0650: }
0651:
0652: /**
0653: * Returns an array of tile <code>Raster</code>s from the cache.
0654: * Any or all of the elements of the returned array may be <code>null</code>
0655: * if the corresponding tile is not in the cache.
0656: *
0657: * @param owner The <code>RenderedImage</code> that the tile belongs to.
0658: * @param tileIndices An array of <code>Point</code>s containing the
0659: * <code>tileX</code> and <code>tileY</code> indices for each tile.
0660: * @since 1.1
0661: */
0662: public synchronized Raster[] getTiles(RenderedImage owner,
0663: Point[] tileIndices) {
0664:
0665: if (memoryCapacity == 0) {
0666: return null;
0667: }
0668:
0669: Raster[] tiles = new Raster[tileIndices.length];
0670:
0671: for (int i = 0; i < tiles.length; i++) {
0672: int tileX = tileIndices[i].x;
0673: int tileY = tileIndices[i].y;
0674:
0675: Object key = SunCachedTile.hashKey(owner, tileX, tileY);
0676: SunCachedTile ct = (SunCachedTile) cache.get(key);
0677:
0678: if (ct == null) {
0679: tiles[i] = null;
0680: missCount++;
0681: } else { // found tile in cache
0682: tiles[i] = (Raster) ct.getTile();
0683:
0684: // Update last-access time. (update() inlined for performance)
0685: ct.timeStamp = timeStamp++;
0686:
0687: if (ct != first) {
0688: // Bring this tile to the beginning of the list.
0689: if (ct == last) {
0690: last = ct.previous;
0691: last.next = null;
0692: } else {
0693: ct.previous.next = ct.next;
0694: ct.next.previous = ct.previous;
0695: }
0696:
0697: ct.previous = null;
0698: ct.next = first;
0699:
0700: first.previous = ct;
0701: first = ct;
0702: }
0703:
0704: hitCount++;
0705:
0706: if (diagnostics) {
0707: ct.action = UPDATE_FROM_GETTILE;
0708: setChanged();
0709: notifyObservers(ct);
0710: }
0711: }
0712: }
0713:
0714: return tiles;
0715: }
0716:
0717: /** Removes -ALL- tiles from the cache. */
0718: public synchronized void flush() {
0719: //
0720: // It is necessary to clear all the elements
0721: // from the old cache in order to remove dangling
0722: // references, due to the linked list. In other
0723: // words, it is possible to reache the object
0724: // through 2 paths so the object does not
0725: // become weakly reachable until the reference
0726: // to it in the hash map is null. It is not enough
0727: // to just set the object to null.
0728: //
0729: Enumeration keys = cache.keys(); // all keys in Hashtable
0730:
0731: // reset counters before diagnostics
0732: hitCount = 0;
0733: missCount = 0;
0734:
0735: while (keys.hasMoreElements()) {
0736: Object key = keys.nextElement();
0737: SunCachedTile ct = (SunCachedTile) cache.remove(key);
0738:
0739: // recalculate memoryUsage only if tile is actually removed
0740: if (ct != null) {
0741: memoryUsage -= ct.memorySize;
0742: tileCount--;
0743:
0744: if (ct == first) {
0745: if (ct == last) {
0746: first = null; // only one tile in the list
0747: last = null;
0748: } else {
0749: first = ct.next;
0750: first.previous = null;
0751: }
0752: } else if (ct == last) {
0753: last = ct.previous;
0754: last.next = null;
0755: } else {
0756: ct.previous.next = ct.next;
0757: ct.next.previous = ct.previous;
0758: }
0759:
0760: ct.previous = null;
0761: ct.next = null;
0762:
0763: // diagnostics
0764: if (diagnostics) {
0765: ct.action = REMOVE_FROM_FLUSH;
0766: setChanged();
0767: notifyObservers(ct);
0768: }
0769: }
0770: }
0771:
0772: if (memoryCapacity > 0) {
0773: cache = new Hashtable(DEFAULT_HASHTABLE_CAPACITY,
0774: LOAD_FACTOR);
0775: }
0776:
0777: if (cacheSortedSet != null) {
0778: cacheSortedSet.clear();
0779: cacheSortedSet = Collections
0780: .synchronizedSortedSet(new TreeSet(comparator));
0781: }
0782:
0783: // force reset after diagnostics
0784: tileCount = 0;
0785: timeStamp = 0;
0786: memoryUsage = 0;
0787:
0788: // no System.gc() here, it's too slow and may occur anyway.
0789: }
0790:
0791: /**
0792: * Returns the cache's tile capacity.
0793: *
0794: * <p> This implementation of <code>TileCache</code> does not use
0795: * the tile capacity. This method always returns 0.
0796: */
0797: public int getTileCapacity() {
0798: return 0;
0799: }
0800:
0801: /**
0802: * Sets the cache's tile capacity to the desired number of tiles.
0803: *
0804: * <p> This implementation of <code>TileCache</code> does not use
0805: * the tile capacity. The cache size is limited by the memory
0806: * capacity only. This method does nothing and has no effect on
0807: * the cache.
0808: *
0809: * @param tileCapacity The desired tile capacity for this cache
0810: * in number of tiles.
0811: */
0812: public void setTileCapacity(int tileCapacity) {
0813: }
0814:
0815: /** Returns the cache's memory capacity in bytes. */
0816: public long getMemoryCapacity() {
0817: return memoryCapacity;
0818: }
0819:
0820: /**
0821: * Sets the cache's memory capacity to the desired number of bytes.
0822: * If the new memory capacity is smaller than the amount of memory
0823: * currently being used by this cache, tiles are removed from the
0824: * cache until the memory usage is less than the specified memory
0825: * capacity.
0826: *
0827: * @param memoryCapacity The desired memory capacity for this cache
0828: * in bytes.
0829: *
0830: * @throws IllegalArgumentException If <code>memoryCapacity</code>
0831: * is less than 0.
0832: */
0833: public void setMemoryCapacity(long memoryCapacity) {
0834: if (memoryCapacity < 0) {
0835: throw new IllegalArgumentException(JaiI18N
0836: .getString("SunTileCache"));
0837: } else if (memoryCapacity == 0) {
0838: flush();
0839: }
0840:
0841: this .memoryCapacity = memoryCapacity;
0842:
0843: if (memoryUsage > memoryCapacity) {
0844: memoryControl();
0845: }
0846: }
0847:
0848: /** Enable Tile Monitoring and Diagnostics */
0849: public void enableDiagnostics() {
0850: diagnostics = true;
0851: }
0852:
0853: /** Turn off diagnostic notification */
0854: public void disableDiagnostics() {
0855: diagnostics = false;
0856: }
0857:
0858: public long getCacheTileCount() {
0859: return tileCount;
0860: }
0861:
0862: public long getCacheMemoryUsed() {
0863: return memoryUsage;
0864: }
0865:
0866: public long getCacheHitCount() {
0867: return hitCount;
0868: }
0869:
0870: public long getCacheMissCount() {
0871: return missCount;
0872: }
0873:
0874: /**
0875: * Reset hit and miss counters.
0876: *
0877: * @since 1.1
0878: */
0879: public void resetCounts() {
0880: hitCount = 0;
0881: missCount = 0;
0882: }
0883:
0884: /**
0885: * Set the memory threshold value.
0886: *
0887: * @since 1.1
0888: */
0889: public void setMemoryThreshold(float mt) {
0890: if (mt < 0.0F || mt > 1.0F) {
0891: throw new IllegalArgumentException(JaiI18N
0892: .getString("SunTileCache"));
0893: } else {
0894: memoryThreshold = mt;
0895: memoryControl();
0896: }
0897: }
0898:
0899: /**
0900: * Returns the current <code>memoryThreshold</code>.
0901: *
0902: * @since 1.1
0903: */
0904: public float getMemoryThreshold() {
0905: return memoryThreshold;
0906: }
0907:
0908: /** Returns a string representation of the class object. */
0909: public String toString() {
0910: return getClass().getName() + "@"
0911: + Integer.toHexString(hashCode())
0912: + ": memoryCapacity = "
0913: + Long.toHexString(memoryCapacity) + " memoryUsage = "
0914: + Long.toHexString(memoryUsage) + " #tilesInCache = "
0915: + Integer.toString(cache.size());
0916: }
0917:
0918: /** Returns the <code>Object</code> that represents the actual cache. */
0919: public Object getCachedObject() {
0920: return cache;
0921: }
0922:
0923: /**
0924: * Removes tiles from the cache based on their last-access time
0925: * (old to new) until the memory usage is memoryThreshold % of that of the
0926: * memory capacity.
0927: */
0928: public synchronized void memoryControl() {
0929: if (cacheSortedSet == null) {
0930: standard_memory_control();
0931: } else {
0932: custom_memory_control();
0933: }
0934: }
0935:
0936: // time stamp based memory control (LRU)
0937: private final void standard_memory_control() {
0938: long limit = (long) (memoryCapacity * memoryThreshold);
0939:
0940: while (memoryUsage > limit && last != null) {
0941: SunCachedTile ct = (SunCachedTile) cache.get(last.key);
0942:
0943: if (ct != null) {
0944: ct = (SunCachedTile) cache.remove(last.key);
0945:
0946: memoryUsage -= last.memorySize;
0947: tileCount--;
0948:
0949: last = last.previous;
0950:
0951: if (last != null) {
0952: last.next.previous = null;
0953: last.next = null;
0954: } else {
0955: first = null;
0956: }
0957:
0958: // diagnostics
0959: if (diagnostics) {
0960: ct.action = REMOVE_FROM_MEMCON;
0961: setChanged();
0962: notifyObservers(ct);
0963: }
0964: }
0965: }
0966: }
0967:
0968: // comparator based memory control (TreeSet)
0969: private final void custom_memory_control() {
0970: long limit = (long) (memoryCapacity * memoryThreshold);
0971: Iterator iter = cacheSortedSet.iterator();
0972: SunCachedTile ct;
0973:
0974: while (iter.hasNext() && (memoryUsage > limit)) {
0975: ct = (SunCachedTile) iter.next();
0976:
0977: memoryUsage -= ct.memorySize;
0978: tileCount--;
0979:
0980: // remove from sorted set
0981: try {
0982: iter.remove();
0983: } catch (ConcurrentModificationException e) {
0984: ImagingListener listener = ImageUtil
0985: .getImagingListener((RenderingHints) null);
0986: listener.errorOccurred(JaiI18N
0987: .getString("SunTileCache0"), e, this , false);
0988: // e.printStackTrace();
0989: }
0990:
0991: // remove tile from the linked list
0992: if (ct == first) {
0993: if (ct == last) {
0994: first = null;
0995: last = null;
0996: } else {
0997: first = ct.next;
0998:
0999: if (first != null) {
1000: first.previous = null;
1001: first.next = ct.next.next;
1002: }
1003: }
1004: } else if (ct == last) {
1005: last = ct.previous;
1006:
1007: if (last != null) {
1008: last.next = null;
1009: last.previous = ct.previous.previous;
1010: }
1011: } else {
1012: SunCachedTile ptr = first.next;
1013:
1014: while (ptr != null) {
1015:
1016: if (ptr == ct) {
1017: if (ptr.previous != null) {
1018: ptr.previous.next = ptr.next;
1019: }
1020:
1021: if (ptr.next != null) {
1022: ptr.next.previous = ptr.previous;
1023: }
1024:
1025: break;
1026: }
1027:
1028: ptr = ptr.next;
1029: }
1030: }
1031:
1032: // remove reference in the hashtable
1033: cache.remove(ct.key);
1034:
1035: // diagnostics
1036: if (diagnostics) {
1037: ct.action = REMOVE_FROM_MEMCON;
1038: setChanged();
1039: notifyObservers(ct);
1040: }
1041: }
1042:
1043: // If the custom memory control didn't release sufficient
1044: // number of tiles to satisfy the memory limit, fallback
1045: // to the standard memory controller.
1046: if (memoryUsage > limit) {
1047: standard_memory_control();
1048: }
1049: }
1050:
1051: /**
1052: * The <code>Comparator</code> is used to produce an
1053: * ordered list of tiles based on a user defined
1054: * compute cost or priority metric. This determines
1055: * which tiles are subject to "ordered" removal
1056: * during a memory control operation.
1057: *
1058: * @since 1.1
1059: */
1060: public synchronized void setTileComparator(Comparator c) {
1061: comparator = c;
1062:
1063: if (comparator == null) {
1064: // turn of comparator
1065: if (cacheSortedSet != null) {
1066: cacheSortedSet.clear();
1067: cacheSortedSet = null;
1068: }
1069: } else {
1070: // copy tiles from hashtable to sorted tree set
1071: cacheSortedSet = Collections
1072: .synchronizedSortedSet(new TreeSet(comparator));
1073:
1074: Enumeration keys = cache.keys();
1075:
1076: while (keys.hasMoreElements()) {
1077: Object key = keys.nextElement();
1078: Object ct = cache.get(key);
1079: cacheSortedSet.add(ct);
1080: }
1081: }
1082: }
1083:
1084: /**
1085: * Return the current comparator
1086: *
1087: * @since 1.1
1088: */
1089: public Comparator getTileComparator() {
1090: return comparator;
1091: }
1092:
1093: // test
1094: public void dump() {
1095:
1096: System.out.println("first = " + first);
1097: System.out.println("last = " + last);
1098:
1099: Iterator iter = cacheSortedSet.iterator();
1100: int k = 0;
1101:
1102: while (iter.hasNext()) {
1103: SunCachedTile ct = (SunCachedTile) iter.next();
1104: System.out.println(k++);
1105: System.out.println(ct);
1106: }
1107: }
1108:
1109: void sendExceptionToListener(String message, Exception e) {
1110: ImagingListener listener = ImageUtil
1111: .getImagingListener((RenderingHints) null);
1112: listener.errorOccurred(message, e, this , false);
1113: }
1114: }
|