/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.client.controls.geopane.cache;
import com.eas.client.controls.geopane.TileUtils;
import com.eas.client.controls.geopane.TilesBoundaries;
import java.awt.Color;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
*
* @author mg
*/
public abstract class TilesCache {
public static final int DEFAULT_CACHE_SIZE = 12 * 12;
public static final int DEF_TILE_SIZE = 256; // 256x256 pixels
// this would take 16 mb with 4-byte full colored images by default
// i.e. 128*10 = 1280 - standard resolution for huge part of monitors
// 10 + 2 for 1 inset over each edge of monitor.
public static final int DEF_TILE_FACTOR = 10;
public static final int DEF_IMAGE_TYPE = BufferedImage.TYPE_INT_RGB;
protected int cacheSize = DEFAULT_CACHE_SIZE;
protected int tileSize = DEF_TILE_SIZE;
protected int tileHalf = DEF_TILE_SIZE / 2;
protected int imageType = DEF_IMAGE_TYPE;
protected Color background = new Color(0, 0, 0, 0);
protected Map<Point, Image> cache = new HashMap<>();
protected TilesBoundaries actualArea;
protected Queue<Image> imagesQueue = new ConcurrentLinkedQueue<>();
protected static int IMAGES_QUEUE_MAX_SIZE = 30;
public TilesCache() {
super();
}
public TilesCache(int aCacheSize) {
super();
cacheSize = aCacheSize;
if (cacheSize <= 0) {
cacheSize = DEFAULT_CACHE_SIZE;
}
}
public void calcTiles(int aWidth, int aHeight) {
if (aWidth > 0 && aHeight > 0) {
int edgeSize = Math.max(aWidth, aHeight);
tileSize = Math.round((float) edgeSize / (float) DEF_TILE_FACTOR);
int edgeSizeInTiles = tileSize + 2;
cacheSize = edgeSizeInTiles * edgeSizeInTiles;
} else {
tileSize = DEF_TILE_SIZE;
cacheSize = DEFAULT_CACHE_SIZE;
}
tileHalf = tileSize / 2;
}
public int getCacheSize() {
return cacheSize;
}
public int getTileSize() {
return tileSize;
}
public void setTileSize(int aTileSize) {
tileSize = aTileSize;
if (tileSize <= 0) {
tileSize = DEF_TILE_SIZE;
}
tileHalf = tileSize / 2;
}
public int getImageType() {
return imageType;
}
/**
* Ordinary shrinking the cache, clearing it if it had grew more than maximum size.
*/
protected void shrink() {
if (cache.size() >= cacheSize) {
Object[] imgs = cache.values().toArray();
cache.clear();
for (Object img : imgs) {
assert img == null || img instanceof Image;
if (img != null && imagesQueue.size() < IMAGES_QUEUE_MAX_SIZE) {
assert !imagesQueue.contains((Image) img);
imagesQueue.add((Image) img);
}
}
}
}
public void setImageType(int aImageType) {
imageType = aImageType;
}
public Point expandPointFromCell(Point ptKey) {
Rectangle rt = TileUtils.expandRectFromCell(ptKey.x, ptKey.y, tileSize);
return new Point(rt.x + rt.width / 2, rt.y + rt.height / 2);
}
protected abstract Image renderTile(Point ptKey);
public Color getBackground() {
return background;
}
public void setBackground(Color aBackground) {
background = aBackground;
}
public abstract void scaleChanged();
// synchronized methods
public synchronized Image get(Point ptKey) {
Image cached = cache.get(ptKey);
if (cached == null) {
cached = renderTile(ptKey);
shrink();
cache.put(ptKey, cached);
}
return cached;
}
public synchronized void put(Point ptKey, Image aImage) {
shrink();
cache.put(ptKey, aImage);
}
public synchronized void clear() {
Object[] imgs = cache.values().toArray();
cache.clear();
for (Object img : imgs) {
assert img == null || img instanceof Image;
if (img != null && imagesQueue.size() < IMAGES_QUEUE_MAX_SIZE) {
assert !imagesQueue.contains((Image) img);
imagesQueue.add((Image) img);
}
}
}
/**
* Special cache operation, intended to riddle cached tiles to avoid clearing
* the cache while ordinary shrinking.
* @param aActualArea TilesBoundaries instance, defining are to maintain im this cache
* @see TilesBoundaries
*/
public synchronized void setActualArea(TilesBoundaries aActualArea) {
List<Point> toDel = new ArrayList<>();
for (Entry<Point, Image> tileEntry : cache.entrySet()) {
Point tileKey = tileEntry.getKey();
if (!aActualArea.contains(tileKey)) {
toDel.add(tileKey);
}
}
for (Point toDelKey : toDel) {
Image img = cache.remove(toDelKey);
// toDelKey key may be invalid and no elements will be removed.
// So we have a simple check here rather than an assert.
if (img != null) {
if (imagesQueue.size() < IMAGES_QUEUE_MAX_SIZE) {
assert !imagesQueue.contains(img);
imagesQueue.add(img);
}
}
}
actualArea = aActualArea;
}
public synchronized TilesBoundaries getActualArea() {
return actualArea;
}
public synchronized int getFactSize() {
return cache.size();
}
}