package org.openstreetmap.gui.jmapviewer;
//License: GPL. Copyright 2008 by Jan Peter Stotz
import java.util.Hashtable;
import java.util.logging.Logger;
import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
/**
* {@link TileCache} implementation that stores all {@link Tile} objects in
* memory up to a certain limit ({@link #getCacheSize()}). If the limit is
* exceeded the least recently used {@link Tile} objects will be deleted.
*
* @author Jan Peter Stotz
*/
public class MemoryTileCache implements TileCache {
protected static final Logger log = Logger.getLogger(MemoryTileCache.class.getName());
/**
* Default cache size
*/
protected int cacheSize = 200;
protected Hashtable<String, CacheEntry> hashtable;
/**
* List of all tiles in their last recently used order
*/
protected CacheLinkedListElement lruTiles;
public MemoryTileCache() {
hashtable = new Hashtable<String, CacheEntry>(cacheSize);
lruTiles = new CacheLinkedListElement();
}
public void addTile(Tile tile) {
CacheEntry entry = createCacheEntry(tile);
hashtable.put(tile.getKey(), entry);
lruTiles.addFirst(entry);
if (hashtable.size() > cacheSize)
removeOldEntries();
}
public Tile getTile(TileSource source, int x, int y, int z) {
CacheEntry entry = hashtable.get(Tile.getTileKey(source, x, y, z));
if (entry == null)
return null;
// We don't care about placeholder tiles and hourglass image tiles, the
// important tiles are the loaded ones
if (entry.tile.isLoaded())
lruTiles.moveElementToFirstPos(entry);
return entry.tile;
}
/**
* Removes the least recently used tiles
*/
protected void removeOldEntries() {
synchronized (lruTiles) {
try {
while (lruTiles.getElementCount() > cacheSize) {
removeEntry(lruTiles.getLastElement());
}
} catch (Exception e) {
log.warning(e.getMessage());
}
}
}
protected void removeEntry(CacheEntry entry) {
hashtable.remove(entry.tile.getKey());
lruTiles.removeEntry(entry);
}
protected CacheEntry createCacheEntry(Tile tile) {
return new CacheEntry(tile);
}
/**
* Clears the cache deleting all tiles from memory
*/
public void clear() {
synchronized (lruTiles) {
hashtable.clear();
lruTiles.clear();
}
}
public int getTileCount() {
return hashtable.size();
}
public int getCacheSize() {
return cacheSize;
}
/**
* Changes the maximum number of {@link Tile} objects that this cache holds.
*
* @param cacheSize
* new maximum number of tiles
*/
public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
if (hashtable.size() > cacheSize)
removeOldEntries();
}
/**
* Linked list element holding the {@link Tile} and links to the
* {@link #next} and {@link #prev} item in the list.
*/
protected static class CacheEntry {
Tile tile;
CacheEntry next;
CacheEntry prev;
protected CacheEntry(Tile tile) {
this.tile = tile;
}
public Tile getTile() {
return tile;
}
public CacheEntry getNext() {
return next;
}
public CacheEntry getPrev() {
return prev;
}
}
/**
* Special implementation of a double linked list for {@link CacheEntry}
* elements. It supports element removal in constant time - in difference to
* the Java implementation which needs O(n).
*
* @author Jan Peter Stotz
*/
protected static class CacheLinkedListElement {
protected CacheEntry firstElement = null;
protected CacheEntry lastElement;
protected int elementCount;
public CacheLinkedListElement() {
clear();
}
public synchronized void clear() {
elementCount = 0;
firstElement = null;
lastElement = null;
}
/**
* Add the element to the head of the list.
*
* @param new element to be added
*/
public synchronized void addFirst(CacheEntry element) {
if (elementCount == 0) {
firstElement = element;
lastElement = element;
element.prev = null;
element.next = null;
} else {
element.next = firstElement;
firstElement.prev = element;
element.prev = null;
firstElement = element;
}
elementCount++;
}
/**
* Removes the specified elemntent form the list.
*
* @param element
* to be removed
*/
public synchronized void removeEntry(CacheEntry element) {
if (element.next != null) {
element.next.prev = element.prev;
}
if (element.prev != null) {
element.prev.next = element.next;
}
if (element == firstElement)
firstElement = element.next;
if (element == lastElement)
lastElement = element.prev;
element.next = null;
element.prev = null;
elementCount--;
}
public synchronized void moveElementToFirstPos(CacheEntry entry) {
if (firstElement == entry)
return;
removeEntry(entry);
addFirst(entry);
}
public int getElementCount() {
return elementCount;
}
public CacheEntry getLastElement() {
return lastElement;
}
public CacheEntry getFirstElement() {
return firstElement;
}
}
}