001 package org.openstreetmap.gui.jmapviewer; 002 003 //License: GPL. Copyright 2008 by Jan Peter Stotz 004 005 import java.util.Hashtable; 006 import java.util.logging.Logger; 007 008 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache; 009 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 010 011 /** 012 * {@link TileCache} implementation that stores all {@link Tile} objects in 013 * memory up to a certain limit ({@link #getCacheSize()}). If the limit is 014 * exceeded the least recently used {@link Tile} objects will be deleted. 015 * 016 * @author Jan Peter Stotz 017 */ 018 public class MemoryTileCache implements TileCache { 019 020 protected static final Logger log = Logger.getLogger(MemoryTileCache.class.getName()); 021 022 /** 023 * Default cache size 024 */ 025 protected int cacheSize = 200; 026 027 protected Hashtable<String, CacheEntry> hashtable; 028 029 /** 030 * List of all tiles in their last recently used order 031 */ 032 protected CacheLinkedListElement lruTiles; 033 034 public MemoryTileCache() { 035 hashtable = new Hashtable<String, CacheEntry>(cacheSize); 036 lruTiles = new CacheLinkedListElement(); 037 } 038 039 public void addTile(Tile tile) { 040 CacheEntry entry = createCacheEntry(tile); 041 hashtable.put(tile.getKey(), entry); 042 lruTiles.addFirst(entry); 043 if (hashtable.size() > cacheSize) 044 removeOldEntries(); 045 } 046 047 public Tile getTile(TileSource source, int x, int y, int z) { 048 CacheEntry entry = hashtable.get(Tile.getTileKey(source, x, y, z)); 049 if (entry == null) 050 return null; 051 // We don't care about placeholder tiles and hourglass image tiles, the 052 // important tiles are the loaded ones 053 if (entry.tile.isLoaded()) 054 lruTiles.moveElementToFirstPos(entry); 055 return entry.tile; 056 } 057 058 /** 059 * Removes the least recently used tiles 060 */ 061 protected void removeOldEntries() { 062 synchronized (lruTiles) { 063 try { 064 while (lruTiles.getElementCount() > cacheSize) { 065 removeEntry(lruTiles.getLastElement()); 066 } 067 } catch (Exception e) { 068 log.warning(e.getMessage()); 069 } 070 } 071 } 072 073 protected void removeEntry(CacheEntry entry) { 074 hashtable.remove(entry.tile.getKey()); 075 lruTiles.removeEntry(entry); 076 } 077 078 protected CacheEntry createCacheEntry(Tile tile) { 079 return new CacheEntry(tile); 080 } 081 082 /** 083 * Clears the cache deleting all tiles from memory 084 */ 085 public void clear() { 086 synchronized (lruTiles) { 087 hashtable.clear(); 088 lruTiles.clear(); 089 } 090 } 091 092 public int getTileCount() { 093 return hashtable.size(); 094 } 095 096 public int getCacheSize() { 097 return cacheSize; 098 } 099 100 /** 101 * Changes the maximum number of {@link Tile} objects that this cache holds. 102 * 103 * @param cacheSize 104 * new maximum number of tiles 105 */ 106 public void setCacheSize(int cacheSize) { 107 this.cacheSize = cacheSize; 108 if (hashtable.size() > cacheSize) 109 removeOldEntries(); 110 } 111 112 /** 113 * Linked list element holding the {@link Tile} and links to the 114 * {@link #next} and {@link #prev} item in the list. 115 */ 116 protected static class CacheEntry { 117 Tile tile; 118 119 CacheEntry next; 120 CacheEntry prev; 121 122 protected CacheEntry(Tile tile) { 123 this.tile = tile; 124 } 125 126 public Tile getTile() { 127 return tile; 128 } 129 130 public CacheEntry getNext() { 131 return next; 132 } 133 134 public CacheEntry getPrev() { 135 return prev; 136 } 137 138 } 139 140 /** 141 * Special implementation of a double linked list for {@link CacheEntry} 142 * elements. It supports element removal in constant time - in difference to 143 * the Java implementation which needs O(n). 144 * 145 * @author Jan Peter Stotz 146 */ 147 protected static class CacheLinkedListElement { 148 protected CacheEntry firstElement = null; 149 protected CacheEntry lastElement; 150 protected int elementCount; 151 152 public CacheLinkedListElement() { 153 clear(); 154 } 155 156 public synchronized void clear() { 157 elementCount = 0; 158 firstElement = null; 159 lastElement = null; 160 } 161 162 /** 163 * Add the element to the head of the list. 164 * 165 * @param element new element to be added 166 */ 167 public synchronized void addFirst(CacheEntry element) { 168 if (elementCount == 0) { 169 firstElement = element; 170 lastElement = element; 171 element.prev = null; 172 element.next = null; 173 } else { 174 element.next = firstElement; 175 firstElement.prev = element; 176 element.prev = null; 177 firstElement = element; 178 } 179 elementCount++; 180 } 181 182 /** 183 * Removes the specified element from the list. 184 * 185 * @param element element to be removed 186 */ 187 public synchronized void removeEntry(CacheEntry element) { 188 if (element.next != null) { 189 element.next.prev = element.prev; 190 } 191 if (element.prev != null) { 192 element.prev.next = element.next; 193 } 194 if (element == firstElement) 195 firstElement = element.next; 196 if (element == lastElement) 197 lastElement = element.prev; 198 element.next = null; 199 element.prev = null; 200 elementCount--; 201 } 202 203 public synchronized void moveElementToFirstPos(CacheEntry entry) { 204 if (firstElement == entry) 205 return; 206 removeEntry(entry); 207 addFirst(entry); 208 } 209 210 public int getElementCount() { 211 return elementCount; 212 } 213 214 public CacheEntry getLastElement() { 215 return lastElement; 216 } 217 218 public CacheEntry getFirstElement() { 219 return firstElement; 220 } 221 222 } 223 }