Worldwind public release 0.2.1
[worldwind-tracker.git] / gov / nasa / worldwind / BasicMemoryCache.java
blob61d96573b4a154e1d7b8de77f05ffba557d8895d
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind;
9 //TODO: write class javadoc description. Remember to include info on: synchronisation, lowWater.
11 /**
12 * @author Eric Dalgliesh
13 * @version $Id: BasicMemoryCache.java 1792 2007-05-08 21:28:37Z tgaskins $
15 public final class BasicMemoryCache implements MemoryCache
17 private static long FALLBACK_CACHE_SIZE = 60000000; // less than applet default max heap size
19 private static class CacheEntry implements Comparable<CacheEntry>
21 Object key;
22 Object clientObject;
23 private long lastUsed;
24 private long clientObjectSize;
26 CacheEntry(Object key, Object clientObject, long clientObjectSize)
28 this.key = key;
29 this.clientObject = clientObject;
30 this.lastUsed = System.nanoTime();
31 this.clientObjectSize = clientObjectSize;
34 /**
35 * @param that
36 * @return
37 * @throws IllegalArgumentException if <code>that</code> is null
39 public int compareTo(CacheEntry that)
41 if (that == null)
43 String msg = WorldWind.retrieveErrMsg("nullValue.CacheEntryIsNull");
44 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
45 throw new IllegalArgumentException(msg);
48 return this.lastUsed < that.lastUsed ? -1 : this.lastUsed == that.lastUsed ? 0 : 1;
51 public String toString()
53 return key.toString() + " " + clientObject.toString() + " " + lastUsed + " " + clientObjectSize;
57 private java.util.concurrent.ConcurrentHashMap<Object, CacheEntry> entries;
58 private java.util.concurrent.CopyOnWriteArrayList<MemoryCache.CacheListener> listeners;
59 private Long capacityInBytes;
60 private Long currentUsedCapacity;
61 private Long lowWater;
63 /**
64 * Contructs a new self-configuring cache. Configuration can be done manually later.
66 public BasicMemoryCache()
68 this.entries = new java.util.concurrent.ConcurrentHashMap<Object, CacheEntry>();
69 this.listeners = new java.util.concurrent.CopyOnWriteArrayList<MemoryCache.CacheListener>();
70 this.capacityInBytes = Configuration.getLongValue(AVKey.CACHE_SIZE, FALLBACK_CACHE_SIZE);
71 this.lowWater = Configuration.getLongValue(AVKey.CACHE_LOW_WATER, (long)(0.7 * FALLBACK_CACHE_SIZE));
72 this.currentUsedCapacity = (long) 0;
75 /**
76 * Constructs a new cache using <code>capacity</code> for maximum size, and <code>loWater</code> for the low water.
78 * @param loWater the low water level
79 * @param capacity the maximum capacity
81 public BasicMemoryCache(long loWater, long capacity)
83 this.entries = new java.util.concurrent.ConcurrentHashMap<Object, CacheEntry>();
84 this.listeners = new java.util.concurrent.CopyOnWriteArrayList<MemoryCache.CacheListener>();
85 this.capacityInBytes = capacity;
86 this.lowWater = loWater;
87 this.currentUsedCapacity = (long) 0;
90 /**
91 * @return the number of objects currently stored in this cache
93 public int getNumObjects()
95 return this.entries.size();
98 /**
99 * @return the capacity of the cache in bytes
101 public long getCapacity()
103 return this.capacityInBytes;
107 * @return the number of bytes that the cache currently holds
109 public synchronized long getUsedCapacity()
111 return this.currentUsedCapacity;
115 * @return the amount of free space left in the cache (in bytes)
117 public synchronized long getFreeCapacity()
119 return this.capacityInBytes - this.currentUsedCapacity;
123 * Adds a cache listener, MemoryCache listeners are used to notify classes when an item is removed from the cache.
125 * @param listener The new <code>CacheListener</code>
126 * @throws IllegalArgumentException is <code>listener</code> is null
128 public synchronized void addCacheListener(MemoryCache.CacheListener listener)
130 if (listener == null)
132 String message = WorldWind.retrieveErrMsg("BasicMemoryCache.nullListenerAdded");
133 WorldWind.logger().log(java.util.logging.Level.FINE, message);
134 throw new IllegalArgumentException(message);
136 this.listeners.add(listener);
140 * Removes a cache listener, objects using this listener will no longer receive notification of cache events.
142 * @param listener The <code>CacheListener</code> to remove
143 * @throws IllegalArgumentException if <code>listener</code> is null
145 public synchronized void removeCacheListener(MemoryCache.CacheListener listener)
147 if (listener == null)
149 String message = WorldWind.retrieveErrMsg("BasicMemoryCache.nullListenerRemoved");
150 WorldWind.logger().log(java.util.logging.Level.FINE, message);
151 throw new IllegalArgumentException(message);
153 this.listeners.remove(listener);
157 * Sets the new capacity (in bytes) for the cache. When decreasing cache size, it is recommended to check that the
158 * lowWater variable is suitable. If the capacity infringes on items stored in the cache, these items are removed.
159 * Setting a new low water is up to the user, that is, it remains unchanged and may be higher than the maximum
160 * capacity. When the low water level is higher than or equal to the maximum capacity, it is ignored, which can lead
161 * to poor performance when adding entries.
163 * @param newCapacity the new capacity of the cache.
165 public synchronized void setCapacity(long newCapacity)
167 this.makeSpace(this.capacityInBytes - newCapacity);
168 this.capacityInBytes = newCapacity;
172 * Sets the new low water level in bytes, which controls how aggresively the cache discards items.
173 * <p/>
174 * When the cache fills, it removes items until it reaches the low water level.
175 * <p/>
176 * Setting a high loWater level will increase cache misses, but decrease average add time, but setting a low loWater
177 * will do the opposite.
179 * @param loWater the new low water level in bytes.
181 public synchronized void setLowWater(long loWater)
183 if (loWater < this.capacityInBytes && loWater >= 0)
185 this.lowWater = loWater;
190 * Returns the low water level in bytes. When the cache fills, it removes items until it reaches the low water
191 * level.
193 * @return the low water level in bytes.
195 public long getLowWater()
197 return this.lowWater;
201 * Returns true if the cache contains the item referenced by key. No guarantee is made as to whether or not the item
202 * will remain in the cache for any period of time.
203 * <p/>
204 * This function does not cause the object referenced by the key to be marked as accessed. <code>getObject()</code>
205 * should be used for that purpose
207 * @param key The key of a specific object
208 * @return true if the cache holds the item referenced by key
209 * @throws IllegalArgumentException if <code>key</code> is null
211 public boolean contains(Object key)
213 if (key == null)
215 String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull");
216 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
217 throw new IllegalArgumentException(msg);
219 return this.entries.containsKey(key);
223 * Adds an object to the cache. The add fails if the object or key is null, or if the size is zero, negative or
224 * greater than the maximmum capacity
226 * @param key The unique reference key that identifies this object.
227 * @param clientObject The actual object to be cached.
228 * @param clientObjectSize The size of the object in bytes.
229 * @return returns true if clientObject was added, false otherwise.
231 public synchronized boolean add(Object key, Object clientObject, long clientObjectSize)
233 if (key == null || clientObject == null || clientObjectSize <= 0 || clientObjectSize > this.capacityInBytes)
235 String msg = WorldWind.retrieveErrMsg("BasicMemoryCache.CacheItemNotAdded");
236 WorldWind.logger().log(java.util.logging.Level.FINER, msg);
238 return false;
239 // the logic behind not throwing an exception is that whether we throw an exception or not,
240 // the object won't be added. This doesn't matter because that object could be removed before
241 // it is accessed again anyway.
244 CacheEntry existing = this.entries.get(key);
245 if (existing != null) // replacing
247 this.removeEntry(existing);
250 if (this.currentUsedCapacity + clientObjectSize > this.capacityInBytes)
252 this.makeSpace(clientObjectSize);
255 this.currentUsedCapacity += clientObjectSize;
256 BasicMemoryCache.CacheEntry entry = new BasicMemoryCache.CacheEntry(key, clientObject, clientObjectSize);
257 this.entries.putIfAbsent(entry.key, entry);
258 return true;
261 public synchronized boolean add(Object key, gov.nasa.worldwind.Cacheable clientObject)
263 return this.add(key, clientObject, clientObject.getSizeInBytes());
267 * Remove the object reference by key from the cache. If no object with the corresponding key is found, this method
268 * returns immediately.
270 * @param key the key of the object to be removed
271 * @throws IllegalArgumentException if <code>key</code> is null
273 public synchronized void remove(Object key)
275 if (key == null)
277 String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull");
278 WorldWind.logger().log(java.util.logging.Level.FINER, msg);
280 return;
283 CacheEntry entry = this.entries.get(key);
284 if (entry != null)
285 this.removeEntry(entry);
289 * Obtain the object referenced by key without removing it. Apart from adding an object, this is the only way to
290 * mark an object as recently used.
292 * @param key The key for the object to be found.
293 * @return the object referenced by key if it is present, null otherwise.
294 * @throws IllegalArgumentException if <code>key</code> is null
296 public synchronized Object getObject(Object key)
298 if (key == null)
300 String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull");
301 WorldWind.logger().log(java.util.logging.Level.FINER, msg);
303 return null;
306 CacheEntry entry = this.entries.get(key);
308 if (entry == null)
309 return null;
311 entry.lastUsed = System.nanoTime(); // nanoTime overflows once every 292 years
312 // which will result in a slowing of the cache
313 // until ww is restarted or the cache is cleared.
314 return entry.clientObject;
318 * Obtain a list of all the keys in the cache.
320 * @return a <code>Set</code> of all keys in the cache.
322 public java.util.Set<Object> getKeySet()
324 return this.entries.keySet();
328 * Empties the cache.
330 public synchronized void clear()
332 for (CacheEntry entry : this.entries.values())
334 this.removeEntry(entry);
339 * Removes <code>entry</code> from the cache. To remove an entry using its key, use <code>remove()</code>
341 * @param entry The entry (as opposed to key) of the item to be removed
343 private synchronized void removeEntry(CacheEntry entry)
345 // all removal passes through this function,
346 // so the reduction in "currentUsedCapacity" and listener notification is done here
348 if (this.entries.remove(entry.key) != null) // returns null if entry does not exist
350 this.currentUsedCapacity -= entry.clientObjectSize;
352 for (MemoryCache.CacheListener listener : this.listeners)
354 listener.entryRemoved(entry.key, entry.clientObject);
360 * Makes at least <code>spaceRequired</code> space in the cache. If spaceRequired is less than (capacity-lowWater),
361 * makes more space. Does nothing if capacity is less than spaceRequired.
363 * @param spaceRequired the amount of space required.
365 private void makeSpace(long spaceRequired)
367 if (spaceRequired > this.capacityInBytes || spaceRequired < 0)
368 return;
370 CacheEntry[] timeOrderedEntries = new CacheEntry[this.entries.size()];
371 java.util.Arrays.sort(this.entries.values().toArray(timeOrderedEntries));
373 int i = 0;
374 while (this.getFreeCapacity() < spaceRequired || this.getUsedCapacity() > this.lowWater)
376 if (i < timeOrderedEntries.length)
378 this.removeEntry(timeOrderedEntries[i++]);
384 * a <code>String</code> representation of this object is returned.&nbsp; This representation consists of maximum
385 * size, current used capacity and number of currently cached items.
387 * @return a <code>String</code> representation of this object
389 @Override
390 public synchronized String toString()
392 return "MemoryCache max size = " + this.getCapacity() + " current size = " + this.currentUsedCapacity
393 + " number of items: " + this.getNumObjects();
396 @Override
397 protected void finalize() throws Throwable
401 // clear doesn't throw any checked exceptions
402 // but this is in case of an unchecked exception
403 // basically, we don't want to exit without calling super.finalize
404 this.clear();
406 finally
408 super.finalize();