Java tutorial
/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.concurrent; import java.awt.Point; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.util.Comparator; import java.util.Iterator; import java.util.Observable; import java.util.Vector; import javax.media.jai.TileCache; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalCause; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import com.google.common.cache.Weigher; import com.sun.media.jai.util.CacheDiagnostics; public class ConcurrentTileCache extends Observable implements TileCache, CacheDiagnostics { /** The default memory threshold of the cache. */ public static final float DEFAULT_MEMORY_THRESHOLD = 0.75F; /** The default memory capacity of the cache (16 MB). */ public static final long DEFAULT_MEMORY_CACHE = 16L * 1024L * 1024L; /** The default diagnostic settings */ public static final boolean DEFAULT_DIAGNOSTIC = false; /** The default concurrency settings */ public static final int DEFAULT_CONCURRENCY_LEVEL = 4; /** * The tile cache. A Guava Cache is used to cache the tiles. The "key" is a <code>Object</code>. The "value" is a CachedTileImpl. */ private Cache<Object, CachedTileImpl> cacheObject; /** The memory capacity of the cache. */ private long memoryCacheCapacity; /** The concurrency level of the cache. */ private int concurrencyLevel; /** The amount of memory to keep after memory control */ private float memoryCacheThreshold = DEFAULT_MEMORY_THRESHOLD; /** diagnosticEnabled enable/disable */ private volatile boolean diagnosticEnabled = DEFAULT_DIAGNOSTIC; /** * The listener is used for receiving notification about the removal of a tile for size constraints */ private final RemovalListener<Object, CachedTileImpl> listener = new RemovalListener<Object, CachedTileImpl>() { public void onRemoval(RemovalNotification<Object, CachedTileImpl> n) { // if a tile is manually removed, the diagnosticEnabled already consider // it in // the remove() method if (diagnosticEnabled) { synchronized (this) { if (n.wasEvicted() && n.getCause() == RemovalCause.SIZE) { CachedTileImpl cti = n.getValue(); cti.setAction(Actions.REMOVAL_FROM_EVICTION); setChanged(); notifyObservers(cti); } } } } }; // diagnostic actions /** A list of all the possible diagnostic actions */ public enum Actions { ADDITION(0), MANUAL_REMOVAL(1), REMOVAL_FROM_FLUSH(2), REMOVAL_FROM_EVICTION(3), SUBSTITUTION_FROM_ADD( 4), UPDATING_TILE_FROM_GETTILE(5), ABOUT_TO_REMOVAL(6); private final int action; Actions(int value) { this.action = value; } public int valueAction() { return action; } }; /** Private cache creation method */ private Cache<Object, CachedTileImpl> buildCache() { CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); builder.maximumWeight((long) (memoryCacheCapacity * memoryCacheThreshold)) .concurrencyLevel(concurrencyLevel).weigher(new Weigher<Object, CachedTileImpl>() { public int weigh(Object o, CachedTileImpl cti) { return (int) cti.getTileSize(); } }); return builder.build(); } public ConcurrentTileCache() { this(DEFAULT_MEMORY_CACHE, DEFAULT_DIAGNOSTIC, DEFAULT_MEMORY_THRESHOLD, DEFAULT_CONCURRENCY_LEVEL); } public ConcurrentTileCache(long memoryCacheCapacity, boolean diagnostic, float mem_threshold, int concurrencyLevel) { if (memoryCacheCapacity < 0) { throw new IllegalArgumentException("Memory capacity too small"); } this.memoryCacheThreshold = mem_threshold; this.diagnosticEnabled = diagnostic; this.memoryCacheCapacity = memoryCacheCapacity; this.concurrencyLevel = concurrencyLevel; // cache creation cacheObject = buildCache(); } /** Add a new tile to the cache */ public void add(RenderedImage owner, int tileX, int tileY, Raster data) { add(owner, tileX, tileY, data, null); } /** Add a new tile to the cache */ public void add(RenderedImage owner, int tileX, int tileY, Raster data, Object tileCacheMetric) { // when computation fails this method is called with a null raster, // avoid logging an extra NPE if (data == null) { return; } // This tile is not in the cache; create a new CachedTileImpl. // else just update. Object key = CachedTileImpl.hashKey(owner, tileX, tileY); // old tile CachedTileImpl cti; // create a new tile CachedTileImpl cti_new = new CachedTileImpl(owner, tileX, tileY, data, tileCacheMetric); // if the tile is already cached if (diagnosticEnabled) { cti = (CachedTileImpl) cacheObject.asMap().put(key, cti_new); synchronized (this) { if (cti != null) { cti.updateTileTimeStamp(); cti.setAction(Actions.SUBSTITUTION_FROM_ADD); setChanged(); notifyObservers(cti); return; } cti_new.setAction(Actions.ADDITION); setChanged(); notifyObservers(cti_new); } } else { // new tile insertion cacheObject.put(key, cti_new); } } /** Removes the selected tile from the cache */ public void remove(RenderedImage owner, int tileX, int tileY) { Object key = CachedTileImpl.hashKey(owner, tileX, tileY); // check if the tile is still in cache CachedTileImpl cti = (CachedTileImpl) cacheObject.getIfPresent(key); // if so the tile is deleted (even if another thread write on it) if (cti != null) { if (diagnosticEnabled) { synchronized (this) { cti.setAction(Actions.ABOUT_TO_REMOVAL); setChanged(); notifyObservers(cti); cti = (CachedTileImpl) cacheObject.asMap().remove(key); if (cti != null) { cti.setAction(Actions.MANUAL_REMOVAL); setChanged(); notifyObservers(cti); } } } else { cacheObject.invalidate(key); } } } /** Retrieves the selected tile from the cache */ public Raster getTile(RenderedImage owner, int tileX, int tileY) { // instantiation of the result raster Raster tileData = null; Object key = CachedTileImpl.hashKey(owner, tileX, tileY); // check if the tile is present CachedTileImpl cti = (CachedTileImpl) cacheObject.getIfPresent(key); if (cti == null) { return null; } if (diagnosticEnabled) { synchronized (this) { // Update last-access time for diagnosticEnabled cti.updateTileTimeStamp(); cti.setAction(Actions.UPDATING_TILE_FROM_GETTILE); setChanged(); notifyObservers(cti); } } // return the selected tile tileData = cti.getTile(); return tileData; } /** * Retrieves an array of all tiles in the cache which are owned by the image. May be <code>null</code> if there were no tiles in the cache. The * array contains no null entries. */ public Raster[] getTiles(RenderedImage owner) { // instantiation of the result array Raster[] tilesData = null; // total number of tiles present in the cache int tileCount = (int) cacheObject.size(); int size = Math.min(owner.getNumXTiles() * owner.getNumYTiles(), tileCount); if (size > 0) { int minTx = owner.getMinTileX(); int minTy = owner.getMinTileY(); int maxTx = minTx + owner.getNumXTiles(); int maxTy = minTy + owner.getNumYTiles(); // arbitrarily set a temporary vector size Vector<Raster> tempData = new Vector<Raster>(10, 20); // cycle through all the tile in the image and check if they are in the // cache... for (int y = minTy; y < maxTy; y++) { for (int x = minTx; x < maxTx; x++) { Raster rasterTile = getTile(owner, x, y); // ...then add to the vector if present if (rasterTile != null) { tempData.add(rasterTile); } } } int tmpsize = tempData.size(); if (tmpsize > 0) { tilesData = (Raster[]) tempData.toArray(new Raster[tmpsize]); } } return tilesData; } /** * Removes all tiles in the cache which are owned by the image. */ public void removeTiles(RenderedImage owner) { if (diagnosticEnabled) { int minTx = owner.getMinTileX(); int minTy = owner.getMinTileY(); int maxTx = minTx + owner.getNumXTiles(); int maxTy = minTy + owner.getNumYTiles(); for (int y = minTy; y < maxTy; y++) { for (int x = minTx; x < maxTx; x++) { remove(owner, x, y); } } } else { int minTx = owner.getMinTileX(); int minTy = owner.getMinTileY(); int maxTx = minTx + owner.getNumXTiles(); int maxTy = minTy + owner.getNumYTiles(); for (int y = minTy; y < maxTy; y++) { for (int x = minTx; x < maxTx; x++) { remove(owner, x, y); } } } } /** * Adds all tiles in the Point array which are owned by the image. */ public void addTiles(RenderedImage owner, Point[] tileIndices, Raster[] tiles, Object tileCacheMetric) { // cycle through the array for adding tiles for (int i = 0; i < tileIndices.length; i++) { int tileX = tileIndices[i].x; int tileY = tileIndices[i].y; Raster tile = tiles[i]; add(owner, tileX, tileY, tile, tileCacheMetric); } } /** * Retrieves an array of tiles in the cache which are specified by the Point array and owned by the image. May be <code>null</code> if there were * not in the cache. The array contains null entries. */ public Raster[] getTiles(RenderedImage owner, Point[] tileIndices) { // instantiation of the array Raster[] tilesData = new Raster[tileIndices.length]; // cycle thorugh the array for getting tiles for (int i = 0; i < tilesData.length; i++) { int tileX = tileIndices[i].x; int tileY = tileIndices[i].y; Raster rasterData = (Raster) getTile(owner, tileX, tileY); // even if the tile is not present it is inserted in the array if (rasterData == null) { tilesData[i] = null; } else { // found tile in cache tilesData[i] = rasterData; } } return tilesData; } /** Removes all tiles present in the cache without checking for the image owner */ public synchronized void flush() { // It is necessary to clear all the elements // from the old cache. if (diagnosticEnabled) { // Creation of an iterator for accessing to every tile in the cache Iterator<Object> keys = cacheObject.asMap().keySet().iterator(); // cycle across the cache for removing and updating every tile while (keys.hasNext()) { Object key = keys.next(); CachedTileImpl cti = (CachedTileImpl) cacheObject.asMap().remove(key); // diagnosticEnabled cti.setAction(Actions.REMOVAL_FROM_FLUSH); setChanged(); notifyObservers(cti); } } else { cacheObject.invalidateAll(); } // cache.invalidateAll(); cacheObject = buildCache(); } /** * Not Supported * * @throws UnsupportedOperationException */ public void memoryControl() { throw new UnsupportedOperationException("Memory Control not supported"); } /** * Not Supported * * @throws UnsupportedOperationException */ public void setTileCapacity(int tileCapacity) { throw new UnsupportedOperationException("Deprecated Operation"); } /** * Not Supported * * @throws UnsupportedOperationException */ public int getTileCapacity() { throw new UnsupportedOperationException("Deprecated Operation"); } /** Sets the cache memory capacity and then flush and rebuild the cache */ public synchronized void setMemoryCapacity(long memoryCacheCapacity) { if (memoryCacheCapacity < 0) { throw new IllegalArgumentException("Memory capacity too small"); } else { this.memoryCacheCapacity = memoryCacheCapacity; flush(); } } /** Retrieve the cache memory capacity */ public long getMemoryCapacity() { return memoryCacheCapacity; } /** Sets the cache memory threshold and then flush and rebuild the cache */ public synchronized void setMemoryThreshold(float mt) { if (mt < 0.0F || mt > 1.0F) { throw new IllegalArgumentException("Memory threshold should be between 0 and 1"); } else { memoryCacheThreshold = mt; flush(); } } /** Retrieve the cache memory threshold */ public float getMemoryThreshold() { return memoryCacheThreshold; } /** Sets the cache ConcurrencyLevel and then flush and rebuild the cache */ public synchronized void setConcurrencyLevel(int concurrency) { if (concurrency < 1) { throw new IllegalArgumentException("ConcurrencyLevel must be at least 1"); } else { concurrencyLevel = concurrency; flush(); } } /** Retrieve the cache concurrency level */ public int getConcurrencyLevel() { return concurrencyLevel; } /** * Not Supported * * @throws UnsupportedOperationException */ public void setTileComparator(Comparator comparator) { throw new UnsupportedOperationException("Comparator not supported"); } /** * Not Supported * * @throws UnsupportedOperationException */ public Comparator getTileComparator() { throw new UnsupportedOperationException("Comparator not supported"); } /** Disables diagnosticEnabled for the observers */ public synchronized void disableDiagnostics() { diagnosticEnabled = false; flush(); } /** Enables diagnosticEnabled for the observers */ public synchronized void enableDiagnostics() { diagnosticEnabled = true; flush(); } /** Retrieves the hit count from the cache statistics */ public long getCacheHitCount() { if (diagnosticEnabled) { return cacheObject.stats().hitCount(); } return 0; } /** Retrieves the current memory size of the cache */ public synchronized long getCacheMemoryUsed() { Iterator<Object> keys = cacheObject.asMap().keySet().iterator(); long memoryUsed = 0; while (keys.hasNext()) { Object key = keys.next(); CachedTileImpl cti = (CachedTileImpl) cacheObject.getIfPresent(key); memoryUsed += cti.getTileSize(); } return memoryUsed; } /** Retrieves the miss count from the cache statistics */ public long getCacheMissCount() { if (diagnosticEnabled) { return cacheObject.stats().missCount(); } return 0; } /** Retrieves the number of tiles in the cache */ public long getCacheTileCount() { return cacheObject.size(); } /** * Not Supported * * @throws UnsupportedOperationException */ public void resetCounts() { throw new UnsupportedOperationException("Operation not supported"); } }