package net.sf.jiga.xtended.kernel;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.io.*;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.swing.AbstractAction;
import javax.swing.JLabel;
import static net.sf.jiga.xtended.kernel.FileHelper.*;
import static net.sf.jiga.xtended.kernel.JXAenvUtils.*;
import net.sf.jiga.xtended.kernel.SpritesCacheManager.SoftValue;
import net.sf.jiga.xtended.ui.Ant;
import net.sf.jiga.xtended.ui.UIMessage;
/**
* Management of cache for many objects, thus the errors of java heap space are
* avoided at the time of the execution:) The listCapacity value will set the
* limit of elements simultaneously to be loaded into the heap memory space,
* then the default value 1 is the best choice if you enable swap (1 living
* element in the heap space while the other will be swapped into files). Other
* values will cause OutOfMemoryError when adding new elements if the heap
* memory space limit is over, then it would be recommended to use more
* frequentely the memorySensitiveCallback to load the elements before to add
* them.
* <i>CAUTION : the Iterators returned by the Collections views of this
* SortedMap implementation of the SpritesCacheManager are fail-fast and
* therefore must be used with caution. </i>
* <b>-Djxa.debugSPM=true</b> to debug this class (rw operations, compress,
* etc..)
*
* @param <K>
* @param <V>
*/
public class SpritesCacheManager<K, V> implements SortedMap<K, V>, Externalizable, Threaded, Debugger {
/**
* @see Ant#JXA_DEBUG_SPM
* @see DebugMap#setDebugLevelEnabled(boolean,
* net.sf.jiga.xtended.kernel.Level)
*/
public final static Level dbLevel = DebugMap._getInstance().newDebugLevel();
static {
boolean b = _getSysBoolean(Ant.JXA_DEBUG_SPM);
DebugMap._getInstance().setDebuggerEnabled(b, SpritesCacheManager.class, dbLevel);
}
/**
* dis/enables the debug mode to std out/err
*
* @default true
* @param b dis/enables the debug mode
*/
public void setDebugEnabled(boolean b) {
DebugMap._getInstance().setDebuggerEnabled(b, SpritesCacheManager.class, dbLevel);
}
/**
* returns true or false whether the debug mode is enabled or not, resp.
*
* @return true or false
*/
public boolean isDebugEnabled() {
return DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class);
}
/**
* serial version uid of this class
*/
private static final long serialVersionUID = 2323;
/**
* @see SpritesCacheListener instances associated to the cache
*/
private transient Set<SpritesCacheListener> listeners = Collections.synchronizedSet(new HashSet<SpritesCacheListener>());
/**
* last thrown error
*/
private Exception lastError;
/**
* list capacity of the implemented maps
*/
private int listCapacity;
/**
* returns true or false whether the compression is enabled or not,
* resp.
*
* @return true or false
* @see #setCompressionEnabled(boolean)
*/
public boolean isCompressedCacheEnabled() {
return compress;
}
/**
* the initial capacity of the cache maps
*/
private int initialListCapacity;
/**
* last recently used objects
*/
private transient Map<KeyRegistry<K>, V> lru;
/**
* last recently used keys
*/
private transient List<KeyRegistry<K>> lruK;
/**
* most recently used objects
*/
private transient Map<KeyRegistry<K>, V> mru;
/**
* most recentrly used keys
*/
private transient List<KeyRegistry<K>> mruK;
/**
* softly cached objects (a synchronized Map view)
*/
private transient Map<KeyRegistry<K>, SoftValue<KeyRegistry<K>, V>> cache;
/**
* references queue to poll by the Garbage Collector
*/
private final static ReferenceQueue _cacheBack = new ReferenceQueue();
/**
* file cached objects
*/
private SortedMap<KeyRegistry<K>, File> cacheDisk;
/**
* compress switch
*
* @default false
*/
private boolean compress = false;
/**
* swap switch
*
* @default false
*/
private boolean swap = false;
/**
* timer instance to use memory auto cleaning up
*
* @deprecated
*/
private transient java.util.Timer timer = null;
/**
* file swapping prefix
*
* @default .sp3
* @deprecated all extensions are with
* {@linkplain JXAenvUtils#_tmpFilesSuffix}
*/
protected String cacheDisk_ext = ".sp3";
/**
* disk directory of file swapping
*
* @deprecated not used, now global
*/
protected String cacheDisk_dir = "cache";
/**
* @default TMP/cache
*/
protected static String _cacheDisk_dir = "cache";
/**
* returns the current cache directory
*
* @return the current cache directory
* @see #clearMemorySwap()
* @see #setCacheDisk_dir(String)
*/
public String getCacheDisk_dir() {
return cacheDisk_dir;
}
/**
* sets up the cache directory
*
* @param cacheDisk_dir the cache directory to set up
* @see #getCacheDisk_dir()
*/
public void setCacheDisk_dir(String cacheDisk_dir) {
this.cacheDisk_dir = cacheDisk_dir;
}
/**
* returns the current prefix for the cache files
*
* @return the current prefix for the cache files
* @see #setCacheDisk_ext(String)
* @deprecated all with {@linkplain JXAenvUtils#_tmpFilesSuffix}
*/
public String getCacheDisk_ext() {
return cacheDisk_ext;
}
/**
* sets up the prefix for the cache files
*
* @param cacheDisk_prefix the prefix for the cache files to set up
* @see #getCacheDisk_prefix()
* @deprecated
* @see #cacheDisk_prefix
*/
public void setCacheDisk_prefix(String cacheDisk_prefix) {
this.cacheDisk_prefix = cacheDisk_prefix;
}
/**
* sets up the extension for the cache files
*
* @param cacheDisk_ext the extension for the cache files to set up
* @see #getCacheDisk_ext()
* @deprecated
* @see #cacheDisk_ext
*/
public void setCacheDisk_ext(String cacheDisk_ext) {
this.cacheDisk_ext = cacheDisk_ext;
}
/**
* returns the current prefix for the cache files
*
* @return the current prefix for the cache files
* @see #setCacheDisk_prefix(String)
* @deprecated
* @see #cacheDisk_prefix
*/
public String getCacheDisk_prefix() {
return cacheDisk_prefix;
}
/**
* files prefix
*
* @deprecated not used, now its global
*/
protected String cacheDisk_prefix = "_cache";
/**
*
*/
protected static String _cacheDisk_prefix = "_cache";
/**
* the write monitor switch
*/
protected transient boolean writing = false;
/**
* the write monitor
*/
protected transient Monitor writeMonitor;
/**
* the read monitor switch
*/
protected transient boolean reading = false;
/**
* the read monitor
*/
protected transient Monitor readMonitor;
/**
* the Collections views sub-maps list
*/
protected transient List<WeakReference<Map<K, V>>> subMaps;
/**
* the Collections views sub-lists of keys list
*/
protected transient List<WeakReference<Set<K>>> keysSubLists;
/**
* the Collectioins views sub-lists of values list
*/
protected transient List<WeakReference<Set<V>>> valuesSubLists;
/**
* logs to the output the specified message
*
* @param message the message to log as debug
* @see #debug(boolean, Object, boolean)
*/
private static void debug(Object message) {
debug(message, true);
}
/**
* logs to the output the specified message
*
* @param message the message to log as debug
* @param newLine dis/enables writing as a new line
* @see PrintStream#print()
* @see PrintStream#println()
*/
private static void debug(Object message, boolean newLine) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
if (newLine) {
System.err.println(message);
} else {
System.err.print(message);
}
}
}
/**
* buffered values are referenced by Reference instances that are linked
* to the main ReferenceQueue so that the buffer is cleaned up every
* time cleanup() is called.* e.g. Object[] value = new Object[100];
* this.buffer(value); value = null; then value is discarded within a
* short delay by the GarbageCollector.
*
* @see #_cacheBack
* @see #cleanup()
*/
private transient Map<Integer, SoftValue> buffer;
/**
* creates a new instance
*
* @param c the Comparator to use for the key sorting
* @param capacity the initial capacity to apply to the maps
*/
public SpritesCacheManager(Comparator<? super K> c, int capacity) {
this(capacity);
comparator = c;
}
/**
* creates a new instance
*
* @param c the Comparator to use for the key sorting
*/
public SpritesCacheManager(Comparator<? super K> c) {
this(c, 1);
}
/**
* no arg contructor , memory will be ranged to one living element in
* the heap LRU_MRU lists
*/
public SpritesCacheManager() {
this(1);
}
/**
* contructs the cache with the specified initialized capacity and a
* WeakHashMap for the main living in the heap MRU_LRU lists.
*
* @see #listCapacity
* @see WeakHashMap
* @param capacity cache memory init and sensitive limit
* @see #memorySensitiveCallback(String, Object, Object[], Class[])
*/
public SpritesCacheManager(int capacity) {
assert capacity != 0 : "capacity cannot be zero";
File d;
if (!(d = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + _cacheDisk_dir)).isDirectory()) {
d.mkdirs();
FileHelper._makeWritable(d);
}
listCapacity = initialListCapacity = capacity;
setGroupMonitor(new Monitor(), new Monitor());
buffer = Collections.synchronizedMap(new HashMap<Integer, SoftValue>());
subMaps = Collections.synchronizedList(new ArrayList<WeakReference<Map<K, V>>>());
keysSubLists = Collections.synchronizedList(new ArrayList<WeakReference<Set<K>>>());
valuesSubLists = Collections.synchronizedList(new ArrayList<WeakReference<Set<V>>>());
lru = Collections.synchronizedMap(new HashMap<KeyRegistry<K>, V>(listCapacity));
lruK = Collections.synchronizedList(new Stack<KeyRegistry<K>>());
mru = Collections.synchronizedMap(new HashMap<KeyRegistry<K>, V>(listCapacity));
mruK = Collections.synchronizedList(new Stack<KeyRegistry<K>>());
cache = Collections.synchronizedMap(new WeakHashMap<KeyRegistry<K>, SoftValue<KeyRegistry<K>, V>>(capacity));
buffer(cache);
cacheDisk = Collections.synchronizedSortedMap(new TreeMap<KeyRegistry<K>, File>());
buffer(cacheDisk);
}
/**
*
*/
public void setGroupMonitor(Monitor... tg) {
writeMonitor = tg[0];
readMonitor = tg[1];
}
/**
* @see #setGroupMonitor(Monitor...)
*/
public Monitor[] getGroupMonitor() {
return new Monitor[]{writeMonitor, readMonitor};
}
/**
* returns the initial list capacity for this SpritesCacheManager
* instance
*
* @return the initial capacity for this SpritesCacheManager instance
* @see #getListCapacity()
* @see #SpritesCacheManager(int)
*/
public int getInitialListCapacity() {
return initialListCapacity;
}
/**
* trunks the cache maps to fit lists capacity
*
* @see #listCapacity
* @param ru recently used map
* @see #lru
* @param ruK recently used keys
* @see #lruK
* @param n desired trunk-size
*/
private void trunk(Map<KeyRegistry<K>, V> ru, List<KeyRegistry<K>> ruK, int n) {
while (ru.size() > n) {
ru.remove(ruK.remove(ruK.size() - 1));
/* fit to cache capacity, pop oldest added entry */
}
cleanup();
}
/**
* the memory sensitive update to the cache Recently Used lists/maps,
* actually adds a strong reference to the object. overflow-protected by
* a memory sensitive-calback
*
* @see #memorySensitiveCallback(Object, Object)
* @param key the key to update to
* @param sp the associated object to update to
*/
private void memorySensitiveUpdate(KeyRegistry<K> key, V sp) throws Throwable {
memorySensitiveCallback("memoryUpdate", this, new Object[]{key, sp}, new Class[]{KeyRegistry.class, Object.class});
}
/**
* memory lists are updated to the given pair K,V, actually adds a
* strong reference to the object V. outdated elements are removed. this
* method needs to be overflow-protected, so we use a memory
* sensitive-callback to avoid overflow.
*
* @see #memorySensitiveUpdate(KeyRegistry, Object)
* @param key the key to update to
* @param sp the associated object to update to
*/
public void memoryUpdate(KeyRegistry<K> key, V sp) {
int currentPty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
if (lru.containsKey(key)) {
/* if the same sprite has been found in last used items then it will be added to the most used items*/
mru.put(key, sp);
mruK.add(0, key);
trunk(mru, mruK, listCapacity);
}
/* here we update last recently used list*/
lru.put(key, sp);
lruK.add(0, key);
trunk(lru, lruK, listCapacity);
Thread.currentThread().setPriority(currentPty);
}
/**
* removes strong references and key references to object , thus it can
* be garbage collected . overflow-protected by a memory
* sensitive-callback.
*
* @see #memorySensitiveCallback(Object, Object)
* @param sp the object to be cleared of memory
* @return the cleared object (same as the parameter)
*/
private V memorySensitiveClear(V sp) throws Throwable {
return (V) memorySensitiveCallback("memoryClear", this, new Object[]{sp}, new Class[]{Object.class});
}
/**
* removes all strong references to the given Object. not
* overflow-protected, we use a memory sensitive-callback.
*
* @see #memorySensitiveClear(Object)
* @param sp the object to be cleared of memory
* @return the cleared object (same as the parameter)
*/
public V memoryClear(V sp) {
int currentPty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
if (lru.containsValue(sp)) {
Set<Map.Entry<KeyRegistry<K>, V>> set = lru.entrySet();
synchronized (lru) {
Iterator<Map.Entry<KeyRegistry<K>, V>> i0 = set.iterator();
for (buffer(i0); i0.hasNext();) {
/* check for any value in LRU*/
Map.Entry<KeyRegistry<K>, V> entry = i0.next();
buffer(entry);
V value = (entry != null) ? entry.getValue() : null;
buffer(value);
if (value != null) {
if (value.equals(sp)) {
synchronized (lruK) {
Iterator<KeyRegistry<K>> i1 = lruK.iterator();
for (buffer(i1); i1.hasNext();) {
/* also remove keys in stack */
if (i1.next().equals(entry.getKey())) {
i1.remove();
}
}
i0.remove();
}
}
}
}
}
}
if (mru.containsValue(sp)) {
Set<Map.Entry<KeyRegistry<K>, V>> set = mru.entrySet();
synchronized (mru) {
Iterator<Map.Entry<KeyRegistry<K>, V>> i0 = set.iterator();
for (buffer(i0); i0.hasNext();) {
/* check for any value in MRU */
Map.Entry<KeyRegistry<K>, V> entry = i0.next();
buffer(entry);
V value = (entry != null) ? entry.getValue() : null;
buffer(value);
if (value != null) {
if (value.equals(sp)) {
synchronized (mruK) {
Iterator<KeyRegistry<K>> i1 = mruK.iterator();
for (buffer(i1); i1.hasNext();) {
/* also remove keys in stack*/
if (i1.next().equals(entry.getKey())) {
i1.remove();
}
}
i0.remove();
}
}
}
}
}
}
Thread.currentThread().setPriority(currentPty);
return sp;
}
/**
* clears the mapping memory
*
* @see Map#clear()
*/
protected void clearMemory() {
mru.clear();
mruK.clear();
lru.clear();
lruK.clear();
}
/**
* compresses cache object
*
* @param out the OutputStream to compress the specified data into
* @param pSp the object to compress
* @return compressed byte array of the argument
* @throws IOException if an error occur while compressing the data
*/
private void compress(final Serializable sp, OutputStream out) {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
cleanup();
buffer(sp);
buffer(out);
try {
ZipEntry ze = new ZipEntry("sp_" + sp.hashCode());
ZipOutputStream zip = new ZipOutputStream(out);
zip.setLevel(Deflater.DEFLATED);
zip.putNextEntry(ze);
buffer(ze);
buffer(zip);
ObjectOutputStream oos = new ObjectOutputStream(zip);
oos.writeObject(sp);
oos.flush();
/**
* cause error oos.close();
*/
zip.closeEntry();
zip.finish();
debug("Compressed caching " + ze.getCompressedSize() + " !");
} catch (IOException ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
} finally {
Thread.currentThread().setPriority(pty);
}
}
/**
* returns the synchronized map of file swapping
*
* @return the file swapping map (contains all the values if swap disk
* is enabled)
*/
public Map<KeyRegistry<K>, File> getSwapMap() {
return cacheDisk;
}
/**
* swap memory disk usage
*/
private long swap_memory_usage = 0;
private static long swap_global_memory_usage = 0;
/**
* returns swap disk usage from SpritesCacheManager in bytes.
*
* @return
* @see JXAenvUtils#_getSwapUsage()
*/
public static long _getSwap_global_memory_usage() {
return swap_global_memory_usage;
}
/**
* returns the total disk usage of the swap memory
*
* @return the total disk usage of the swap memory
* @see #getSwapMap()
* @see File#length()
*/
public long getSwapUsage() {
return swap_memory_usage;
}
/**
* allows to, whenever it is possible, to load from
* ExtensionsClassLoader serialized objects from IO streams.
*
* @see ExtensionsClassLoader.ObjectInputStream
*/
public final static String JXA_ECLIO = "jxa.ecl.io";
private static boolean isExtClassLoadIO() {
return Boolean.getBoolean(JXA_ECLIO);
}
/**
* uncompresses cache object
*
* @param in the InputStream to read from
* @return the uncompressed object
*/
private Serializable uncompress(InputStream in) throws NullPointerException {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
cleanup();
Serializable o = null;
ZipInputStream zis = new ZipInputStream(in);
buffer(zis);
try {
zis.getNextEntry();
ObjectInputStream ois = !isExtClassLoadIO() ? new ObjectInputStream(zis) : ExtensionsClassLoader.getInstance().new ObjectInputStream(zis);
buffer(ois);
o = (Serializable) ois.readObject();
buffer(o);
/* cause error ois.close();*/
zis.closeEntry();
zis.close();
} catch (EOFException e) {
debug("CacheEntry: uncompress: done.");
} catch (IOException e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
} finally {
if (o == null) {
throw new NullPointerException("Null CacheEntry: cannot uncompress!");
}
Thread.currentThread().setPriority(pty);
return o;
}
}
/**
* the SpritesCacheManager instance timestamp identifier
*/
private long hash = System.nanoTime();
/**
* returns the SpritesCacheManager instance timestamp identifier
*
* @return the timestamp identifier of this SpritesCacheManager instance
*/
public int hashCode() {
return (int) hash;
}
/**
* returns true or false whether this SpritesCacheManager instance is
* equal to the specified object or not, resp.
*
* @return true or false
* @see #hashCode()
*/
public boolean equals(Object o) {
return o != null ? hash == o.hashCode() : false;
}
/**
* writes the specified mapping to swap.
*
* @param key the key
* @param value the associated object
* @return true or false whether it succeeded or not, resp.
*/
public boolean writeSwap(final KeyRegistry<K> key, final Serializable value) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return _writeSwap(key, value);
}
});
}
/**
* writes the specified mapping to swap.
*
* @param key the key
* @param value the associated object
* @return true or false whether it succeeded or not, resp.
* @throws Exception if an error occur while writing the data
*/
private boolean _writeSwap(KeyRegistry<K> key, Serializable value) {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
cleanup();
notifyWrite(SpritesCacheListener.WRITE_STARTED);
buffer(key);
buffer(value);
File f = null;
final boolean compress0 = compress;
try {
final Monitor monitor0 = readMonitor;
synchronized (monitor0) {
while (reading) {
monitor0.wait();
}
writing = true;
File dir = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + _cacheDisk_dir);
dir.mkdirs();
FileHelper._makeWritable(dir);
f = FileHelper._createTempFile(cacheDisk_prefix + hashCode(), dir, true);
RandomAccessFile raf = new RandomAccessFile(f, "rw");
FileOutputStream fos = new FileOutputStream(raf.getFD());
BufferedOutputStream bos = new BufferedOutputStream(fos, FileHelper._SMALLBUFFFER_SIZE);
ObjectOutputStream oos = new ObjectOutputStream(bos);
buffer(oos);
buffer(bos);
oos.writeBoolean(compress0);
if (compress0) {
compress(value, oos);
} else {
oos.writeObject(value);
}
oos.flush();
oos.close();
bos.close();
raf.close();
File old = cacheDisk.put(key, f);
if (old instanceof File) {
if (!old.equals(f)) {
if (FileHelper._accessFilePermitted(old, FILE_DELETE)) {
old.delete();
swap_memory_usage -= old.length();
swap_global_memory_usage -= old.length();
}
}
}
swap_memory_usage += f.length();
swap_global_memory_usage += f.length();
notifyWrite(SpritesCacheListener.WRITE_COMPLETED);
value = null;
Thread.currentThread().setPriority(pty);
monitor0.notify();
}
final Monitor monitor1 = writeMonitor;
synchronized (monitor1) {
writing = false;
monitor1.notifyAll();
}
return true;
} catch (Exception e) {
if (e instanceof InterruptedException) {
if (isDebugEnabled()) {
e.printStackTrace();
}
} else {
if (isDebugEnabled()) {
e.printStackTrace();
}
}
lastError = e;
notifyWrite(SpritesCacheListener.WRITE_ERROR);
if (f != null) {
f.delete();
swap_memory_usage -= f.length();
swap_global_memory_usage -= f.length();
notifyWrite(SpritesCacheListener.WRITE_ABORTED);
}
final Monitor monitor = writeMonitor;
synchronized (monitor) {
writing = false;
monitor.notifyAll();
}
return false;
}
}
/**
* reads swap.
*
* @param key the key to read from
* @return the read value-object
*/
public V readSwap(final KeyRegistry<K> key) {
return AccessController.doPrivileged(new PrivilegedAction<V>() {
public V run() {
return _readSwap(key);
}
});
}
/**
* reads swap.
*
* @param key the key to read from
* @return the read value-object
* @throws Exception if an error occur while reading the data
*/
private V _readSwap(KeyRegistry<K> key) {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
cleanup();
notifyRead(SpritesCacheListener.READ_STARTED);
V value = null;
File f = null;
try {
final Monitor monitor0 = writeMonitor;
synchronized (monitor0) {
while (writing) {
monitor0.wait();
}
reading = true;
if (cacheDisk.containsKey(key)) {
debug("Swap: Found Key " + key + " ");
f = (File) cacheDisk.get(key);
debug(f.getCanonicalPath());
RandomAccessFile raf = new RandomAccessFile(f, "r");
FileInputStream fis = new FileInputStream(raf.getFD());
BufferedInputStream bis = new BufferedInputStream(fis, FileHelper._SMALLBUFFFER_SIZE);
debug("opening...");
ObjectInputStream ois = !isExtClassLoadIO() ? new ObjectInputStream(bis) : ExtensionsClassLoader.getInstance().new ObjectInputStream(bis);
buffer(ois);
buffer(bis);
if (ois.readBoolean()) {
value = (V) uncompress(ois);
} else {
value = (V) ois.readObject();
}
buffer(value);
ois.close();
bis.close();
raf.close();
}
monitor0.notify();
}
} catch (Exception e) {
if (e instanceof OptionalDataException) {
OptionalDataException opt = (OptionalDataException) e;
debug("OPTIONAL DATA EXCEPTION : eof=" + opt.eof + " bytes=" + opt.length);
} else if (e instanceof InterruptedException) {
if (isDebugEnabled()) {
e.printStackTrace();
}
value = null;
} else {
if (isDebugEnabled()) {
e.printStackTrace();
}
}
lastError = e;
notifyRead(SpritesCacheListener.READ_ERROR);
} finally {
if (value == null) {
notifyRead(SpritesCacheListener.READ_ABORTED);
} else {
debug("Swap: reading done.");
notifyRead(SpritesCacheListener.READ_COMPLETED);
}
Thread.currentThread().setPriority(pty);
final Monitor monitor1 = readMonitor;
synchronized (monitor1) {
reading = false;
monitor1.notifyAll();
}
return value;
}
}
/**
* removes file swapping for the specified key
*
* @param key the key to remove from file swapping
* @return true or false whether it has succeeded or not, resp.
*/
private boolean unswap(final KeyRegistry<K> key) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return _unswap(key);
}
});
}
/**
* removes file swapping for the specified key
*
* @param key the key to remove from file swapping
* @return true or false whether it has succeeded or not, resp.
*/
private boolean _unswap(KeyRegistry<K> key) {
File f = cacheDisk.remove(key);
try {
if (f instanceof File) {
if (FileHelper._accessFilePermitted(f, FILE_DELETE)) {
f.delete();
swap_memory_usage -= f.length();
swap_global_memory_usage -= f.length();
} else {
f.deleteOnExit();
}
return true;
} else {
return false;
}
} catch (Exception e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
return false;
}
}
/**
* dis/enables compression of the cache entries
*
* @param b compression dis/enabled
*/
public void setCompressionEnabled(boolean b) {
compress = b;
}
/**
* returns the shutdown hooker (to clean the file swap)
*
* @return Thread as shutdown hooker to run
*/
private Thread getShutdownHooker() {
return new Thread(new Runnable() {
public void run() {
SpritesCacheManager.this.clearMemorySwap();
}
});
}
/**
* dis/enables file swap to disk of the cache entries (enabled is
* recommended)
*
* @param b swapping dis/enabled
*/
public void setSwapDiskEnabled(final boolean b) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
if (b) {
if (!swap) {
Runtime.getRuntime().addShutdownHook(getShutdownHooker());
}
} else {
Runtime.getRuntime().removeShutdownHook(getShutdownHooker());
}
swap = b;
return null;
}
});
}
/**
* returns true or false whether swapping is enabled or not, resp.
*
* @see #setSwapDiskEnabled(boolean)
* @return true or false
*/
public boolean isSwapDiskEnabled() {
return swap;
}
/**
* returns the current opened cache size
*
* @return size of the living cache (excluding file swap cache)
* @see #cleanup()
*/
public int size() {
try {
cleanup();
} catch (Exception e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
} finally {
return cache.size();
}
}
/**
* returns the memory heap allocation size, i.e. the percentage of the
* total capacity
*
* @return the current memory allocation, i.e. percent of the lists
* capactity
* @see #listCapacity
* @see #size()
*/
public double allocSize() {
double d = 100 * cache.size() / listCapacity;
debug("alloc=" + d);
return d;
}
/**
* adds the specified mapping to cache, and swap it to the disk cache if
* swapping is enabled
*
* @see #setSwapDiskEnabled(boolean)
* @param key the key
* @param obj the associated object
* @return the previous mapped instance or null
* @see #add(Object, Object, boolean)
*/
public V add(K key, V obj) {
return add(key, obj, swap);
}
/**
* updates the current sub-maps viewing this SpritesCacheManager
* instance
*
* @param removal dis/enables removal of the mapping
* @param key the targeted key
* @param value the associated value
* @see #subMaps
*/
private void updateSubMaps(boolean removal, K key, V value) {
synchronized (subMaps) {
for (Iterator<WeakReference<Map<K, V>>> i = subMaps.iterator(); i.hasNext();) {
WeakReference<Map<K, V>> ref = i.next();
Map<K, V> subMap = ref.get();
if (subMap instanceof Map && key != null && value != null) {
if (removal) {
subMap.remove(key);
} else {
subMap.put(key, value);
}
}
}
}
}
/**
* updates the current sub-lists of keys viewing this
* SpritesCacheManager instance
*
* @param removal dis/enables removal of the mapping
* @param key the target key
* @see #keysSubLists
*/
private void updateSubListsK(boolean removal, K key) {
synchronized (keysSubLists) {
for (Iterator<WeakReference<Set<K>>> i = keysSubLists.iterator(); i.hasNext();) {
WeakReference<Set<K>> listRef = i.next();
Set<K> subList = listRef.get();
if (subList instanceof Set && key != null) {
if (removal) {
subList.remove(key);
} else {
subList.add(key);
}
}
}
}
}
/**
* updates the current sub-lists of values viewing this
* SpritesCacheManager instance
*
* @param removal dis/enables removal of the mapping
* @param value the targeted value
* @see #valuesSubLists
*/
private void updateSubListsV(boolean removal, V value) {
synchronized (valuesSubLists) {
for (Iterator<WeakReference<Set<V>>> i = valuesSubLists.iterator(); i.hasNext();) {
WeakReference<Set<V>> listRef = i.next();
Set<V> subList = listRef.get();
if (subList instanceof Set && value != null) {
if (removal) {
subList.remove(value);
} else {
subList.add(value);
}
}
}
}
}
/**
* adds the mapping to cache with the swap option to dis/enabled
*
* @param key the targeted key
* @param obj the associated object
* @param swap option to dis/enable swap for this mapping
* @return the previous mapped object or null
*/
protected V add(K key, V obj, boolean swap) {
cleanup();
SoftValue<KeyRegistry<K>, V> ancient = null;
KeyRegistry<K> keyReg = new KeyRegistry(key);
try {
if (obj != null) {
if (swap) {
if (memorySensitiveCallback("writeSwap", this, new Object[]{keyReg, (Serializable) obj}, new Class[]{KeyRegistry.class, Serializable.class}).equals(Boolean.FALSE)) {
throw new Exception("Unable to swap! " + key);
}
}
/*memorySensitiveUpdate(keyReg, obj); do not set strong reference on adding*/
ancient = (SoftValue<KeyRegistry<K>, V>) cache.put(keyReg, new SoftValue(obj, keyReg));
buffer(ancient);
updateSubMaps(false, key, obj);
updateSubListsK(false, key);
updateSubListsV(false, obj);
if (ancient != null) {
V v = ancient.get();
if (v != null) {
if (!v.equals(obj)) {
memorySensitiveClear(ancient.get());
}
}
}
}
} catch (Exception e) {
debug("CacheEntry: error while caching " + key + e.getMessage() + "\r\n");
if (isDebugEnabled()) {
e.printStackTrace();
}
} finally {
return (ancient instanceof WeakReference) ? ancient.get() : null;
}
}
/**
* safely removes the specified Object from the cache (it can be either
* a key referencing an object or an cached object)
*
* @param sp can be either a key or a value; if the key-class is the
* same as value-class then it will stands for a key
* @return the removed object or null
* @discussion all current Collections views are updated
*/
public V remove(Object sp) {
try {
KeyRegistry<K> key = new KeyRegistry(sp);
SoftValue<KeyRegistry<K>, V> ref = cache.remove(key);
updateSubMaps(true, key.getKey(), null);
updateSubListsK(true, key.getKey());
V value = (ref instanceof SoftValue) ? ref.get() == null ? null : ref.get() : null;
updateSubListsV(true, value);
synchronized (lruK) {
Iterator<KeyRegistry<K>> i = lruK.iterator();
for (buffer(i); i.hasNext();) {
if (i.next().equals(key)) {
i.remove();
}
}
}
synchronized (mruK) {
Iterator<KeyRegistry<K>> i = mruK.iterator();
for (buffer(i); i.hasNext();) {
if (i.next().equals(key)) {
i.remove();
}
}
}
lru.remove(key);
mru.remove(key);
if (swap) {
unswap(key);
}
return value;
} catch (ClassCastException e) {
try {
return memorySensitiveClear((V) sp);
} catch (Throwable t) {
if (isDebugEnabled()) {
t.printStackTrace();
}
return null;
}
}
}
/**
* returns the k-referenced object
*
* @param k the key that references the object
* @return V the referenced object
*/
public V get(Object k) {
cleanup();
KeyRegistry<K> key = new KeyRegistry(k);
V value = null;
if (cache.containsKey(key)) {
value = cache.get(key).get();
}
if (value == null) {
if (swap && cacheDisk.containsKey(key)) {
debug("Get key " + key + " from swap...");
try {
value = (V) memorySensitiveCallback("readSwap", this, new Object[]{key}, new Class[]{KeyRegistry.class});
add(key.getKey(), value, false);
} catch (Throwable ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
}
}
}
if (value != null) {
try {
memorySensitiveUpdate(key, value);
} catch (Throwable t) {
if (isDebugEnabled()) {
t.printStackTrace();
}
}
}
return value;
}
/**
* returns true or false whether this key is available or not, resp.
*
* @param key the key to look for
* @return true or false
* @see SortedMap#containsKey(Object)
*/
private boolean has(KeyRegistry<K> key) {
cleanup();
return (cache.containsKey(key)) ? true : ((swap) ? cacheDisk.containsKey(key) : false);
}
/**
* clears the swap memory; all files swapped on the disk that this cache
* instance currently uses are deleted. CAUTION : deleting the file
* memory swap files should not be used for a synchronization purpose.
*
* @see #setSwapDiskEnabled(boolean)
*/
public void clearMemorySwap() {
if (swap) {
Set<Map.Entry<KeyRegistry<K>, File>> set = cacheDisk.entrySet();
synchronized (cacheDisk) {
for (Iterator<Map.Entry<KeyRegistry<K>, File>> i = set.iterator(); i.hasNext();) {
Map.Entry<KeyRegistry<K>, File> entry = i.next();
File f = entry.getValue();
if (!fileCache.contains(f.getPath())) {
if (FileHelper._accessFilePermitted(f, FILE_DELETE)) {
f.delete();
swap_memory_usage -= f.length();
swap_global_memory_usage -= f.length();
}
i.remove();
}
}
}
}
}
/**
* clears all the sub-maps viewing this SpritesCacheManager instance
*
* @see #subMaps
*/
private void clearSubMaps() {
synchronized (subMaps) {
for (Iterator<WeakReference<Map<K, V>>> i = subMaps.iterator(); i.hasNext();) {
WeakReference<Map<K, V>> mapRef = i.next();
Map<K, V> subMap = mapRef.get();
if (subMap instanceof Map) {
subMap.clear();
}
}
}
}
/**
* clears all the sub-lists of keys viewing this SpritesCacheManager
* instance
*
* @see #keysSubLists
*/
private void clearSubListsK() {
synchronized (keysSubLists) {
for (Iterator<WeakReference<Set<K>>> i = keysSubLists.iterator(); i.hasNext();) {
WeakReference<Set<K>> listRef = i.next();
Set<K> subList = listRef.get();
if (subList instanceof Set) {
subList.clear();
}
}
}
}
/**
* clears all the sub-lists of values viewing this SpritesCacheManager
* instance
*
* @see #valuesSubLists
*/
private void clearSubListsV() {
synchronized (valuesSubLists) {
for (Iterator<WeakReference<Set<V>>> i = valuesSubLists.iterator(); i.hasNext();) {
WeakReference<Set<V>> listRef = i.next();
Set<V> subList = listRef.get();
if (subList instanceof Set) {
subList.clear();
}
}
}
}
/**
* clears the heap memory maps, Actually all strong references are
* discarded, then a GC is attempted.
*
* @see SortedMap#clear()
* @see #cleanup()
*/
public void clear() {
clearMemory();
/*slow System.gc();*/
cleanup();
cache.clear();
}
/**
* deletes garbage. Image are flushed, Closeable (Input/OutputStream)
* are closed.
*
* @see SoftValue references are kept alive through a clear callback to
* enqueue()
*/
public static void _cleanup() {
Reference ref;
while ((ref = _cacheBack.poll()) != null) {
Object o = ref.get();
if (o instanceof Image) {
((Image) o).flush();
}
if (o instanceof Closeable) {
try {
((Closeable) o).close();
} catch (IOException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
}
}
}
}
/**
* safely cleans up the cache to clear polled referenced-objects
*
* @see ReferenceQueue#poll()
*/
public void cleanup() {
int currentPty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
_cleanup();
synchronized (buffer) {
for (Iterator<Integer> it = buffer.keySet().iterator(); it.hasNext();) {
int k = it.next();
if (buffer.get(k).get() == null) {
it.remove();
}
}
}
synchronized (keysSubLists) {
for (Iterator<WeakReference<Set<K>>> it = keysSubLists.iterator(); it.hasNext();) {
WeakReference<Set<K>> s = it.next();
if (s.get() == null) {
it.remove();
}
}
}
synchronized (valuesSubLists) {
for (Iterator<WeakReference<Set<V>>> it = valuesSubLists.iterator(); it.hasNext();) {
WeakReference<Set<V>> s = it.next();
if (s.get() == null) {
it.remove();
}
}
}
synchronized (subMaps) {
for (Iterator<WeakReference<Map<K, V>>> it = subMaps.iterator(); it.hasNext();) {
WeakReference<Map<K, V>> s = it.next();
if (s.get() == null) {
it.remove();
}
}
}
_cleanup();
Thread.currentThread().setPriority(currentPty);
}
/**
* dis/enables automatic call-back to {@link #cleanup() cleanup} of
* garbage queue originally done by the Garbage Collector (enabling this
* can cause a global thread overflow)
*
* @see #cleanup()
* @param b dis/enables automatic cleanup (high Thread usage)
* @deprecated not used
*/
public void setAutoCleanupEnabled(boolean b) {
if (timer != null) {
timer.cancel();
timer.purge();
}
if (b) {
timer = new java.util.Timer("Timer-SpritesCacheManager-Cleanup");
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 1);
cleanup();
}
}, 0L, 250);
buffer(timer);
} else {
timer = null;
}
}
/**
* ensures the list capacity not to get too small and to guaranty the
* respect of the contract of the initial list capacity of Map's
* (affects heap memory usage). a
* {@link SpritesCacheListener#CAPACITY_EXTENDED capacity has been extended}
* event will be sent to the SpritesCacheListener's.
*
* @param n the list capacity to ensure (best choice is to specify the
* {@link #getInitialListCapacity() initial capacity})
*/
public void ensureListCapacity(int n) {
listCapacity = Math.max(listCapacity, n);
notifyEvent(SpritesCacheListener.CAPACITY_EXTENDED);
}
/**
* cut-off the lists capacity to the specified value (amount of objects
* that will stay alive unless they can cause a heap overflow)
*
* @param n amount of heap objects to keep alive in the cache,
* over-flowing objects are discarded
* @see #trunk(Map, List, int)
*/
public void trimCacheLive(int n) {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
trunk(mru, mruK, n);
trunk(lru, lruK, n);
listCapacity = n;
notifyEvent(SpritesCacheListener.CAPACITY_EXTENDED);
Thread.currentThread().setPriority(pty);
}
/**
* call back to any method.
*
* @param method
* @param args
* @param target
* @param clargs
* @return
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @see SpritesCacheManager#_callback(String, Object, Object[], Class[])
*/
public static Object callback(String method, Object target, Object[] args, Class[] clargs) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return _callback(method, target, args, clargs);
}
/**
* sets up the priority of the invoking task
*
* @see Thread#setPriority(int)
* @param method the method named to make the callback
* @param target the targeted object by this method callback
* @param args the Objects arguments of the method callback
* @param clargs the Class arguments of the method callback
* @param level priority value from 0 to 9. (normal value is that one of
* Thread.NORM_PRIORITY)
* @return the returned instance of the called-back method
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @see #callback(String, Object, Object[], Class[])
*
*/
public static Object doPriority(String method, Object target, Object[] args, Class[] clargs, int level) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return _doPriority(method, target, args, clargs, level);
}
/**
* this is part of the most important functions of this cache. It calls
* back to invoked method with memory sensitivity selfcare, i.e. memory
* will not cause a crash of the JVM due to the memory limit defined by
* the listCapacity value. And eventually the callback will throw
* OutOfMemory to the cache, which is able to clear heap memory
* overflows. Least recently used elements will be discarded to clear
* memory space if they're not referenced elsewhere than in the cache.
* CAUTION : you SHOULD NOT use a callback method with any of the
* present cache class-methods, because of synchronization issues with
* Java that lock the Map instance used by this cache.
*
* @param method the method named to make the callback
* @param target the targeted object by this method callback
* @param args the Objects arguments of the method callback
* @param clargs the Class arguments of the method callback
* @return the returned instance of the called-back method
* @see #callback(String, Object, Object[], Class[])
* @throws Throwable
* @throws NoSuchMethodException thrown by
* {@link #callback(String, Object, Object[], Class[]) callback}
* @throws IllegalArgumentException thrown by
* {@link #callback(String, Object, Object[], Class[]) callback}
* @throws IllegalAccessException thrown by
* {@link #callback(String, Object, Object[], Class[]) callback}
* @throws InvocationTargetException thrown by
* {@link #callback(String, Object, Object[], Class[]) callback}
*/
public Object memorySensitiveCallback(String method, Object target, Object[] args, Class[] clargs) throws Throwable {
Object ret = __memorySensitiveCallback(method, target, args, clargs);
ensureListCapacity(initialListCapacity);
return ret;
}
private Object __memorySensitiveCallback(String method, Object target, Object[] args, Class[] clargs) throws Throwable {
try {
Object o = callback(method, target, args, clargs);
cleanup();
return o;
} catch (NoSuchMethodException ex) {
ex.printStackTrace();
return null;
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
return null;
} catch (IllegalAccessException ex) {
ex.printStackTrace();
return null;
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
buffer(t);
if (t instanceof OutOfMemoryError) {
if (isDebugEnabled()) {
System.err.println("!!!!!!! " + getClass().getName() + " caught OutOfMemory error, trying to collect garbage (System.gc())...");
}
if (isDebugEnabled()) {
t.printStackTrace();
}
if (listCapacity <= 1) {
clearMemory();
if (isDebugEnabled()) {
System.err.println("!!!!!!! " + toString() + " cache capacity is dangerously low...");
}
UIMessage.showLightPopupMessage(new JLabel("<html><b>FATAL ERROR :</b> <br>Memory seems to be overlflown, task is interrupted now.</html>"), new AbstractAction("Exit") {
public void actionPerformed(ActionEvent e) {
Runtime.getRuntime().halt(666);
}
}, null, 0);
Thread.currentThread().interrupt();
System.gc();
System.runFinalization();
System.gc();
throw t;
} else {
trimCacheLive((int) Math.ceil((float) listCapacity / 2.0f));
}
System.gc();
if (isDebugEnabled()) {
System.err.println("!!!!!!! Garbage has been collected by the System. cleanup.");
}
cleanup();
System.runFinalization();
System.gc();
return __memorySensitiveCallback(method, target, args, clargs);
} else {
throw t;
}
}
}
/**
* the finalization method clears the cache and stops any running
* activity NOTICE: the swap files are deleted
*/
protected void finalize() throws Throwable {
if (timer != null) {
timer.cancel();
timer = null;
}
clear();
clearSubMaps();
clearSubListsK();
clearSubListsV();
clearMemorySwap();
super.finalize();
}
/**
* cleans up the file swapping directory. CAUTION : this method
* IMMEDIATELY deletes all files located in the swapping directory. all
* cache instances that use the same directory may fail to recover after
* using this method. {@link #clearMemorySwap() use clearMemorySwap()}
* to safely delete all files used by this cache instance.
*/
public static void _cleanFileSwap() {
File dir = null;
swap_global_memory_usage = 0;
dir = new File(FileHelper._TMPDIRECTORY + File.separator + _cacheDisk_dir);
File[] swapFiles = null;
if (dir.isDirectory()) {
swapFiles = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(_cacheDisk_prefix);
}
});
}
for (File f : swapFiles) {
try {
if (FileHelper._accessFilePermitted(f, FILE_DELETE)) {
f.delete();
swap_global_memory_usage -= f.length();
}
} catch (Exception e) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
e.printStackTrace();
}
}
}
}
/**
* registers the specified object to avoid memory freeze when it has to
* be cleared off the memory.
*
* @discussion the buffer is saved into a synchronized HashMap and when
* the reference is lost, the ReferenceQueue discards the instance.
* @param obj the object to buffer
* @see SoftValue
* @see ReferenceQueue
* @see #cleanup()
*/
public void buffer(Object obj) {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
cleanup();
if (obj != null) {
buffer.put(obj.hashCode(), new SoftValue(obj, obj.hashCode()));
}
Thread.currentThread().setPriority(pty);
}
/**
* returns true or false whether the cache is empty or not, resp.
*
* @return true or false
* @see Map#isEmpty()
*/
public boolean isEmpty() {
return (swap) ? cacheDisk.isEmpty() : cache.isEmpty();
}
/**
* returns true or false whether the specified key is mapped or not,
* resp. CAUTION : unless swap disk is disabled, the key can be found as
* swapped memory.
*
* @param key the key to check for availability
* @return true or false
*/
public boolean containsKey(Object key) {
if (key == null) {
return false;
}
return has(new KeyRegistry(key));
}
/**
* returns true or false whether the specified value is found in the
* living cache. CAUTION : this can cost a lot of memory swapping, use
* containsKey to avoid this.
*
* @param object the object to check for availibity in the living cache
* memory
* @return true or false
*/
public boolean containsValue(Object object) {
Set<K> coll = keySet();
synchronized (coll) {
for (Iterator<K> i = coll.iterator(); i.hasNext();) {
Object ref = get(i.next());
if (ref != null) {
if (ref.equals(object)) {
return true;
}
}
}
}
return false;
}
/**
* adds a new mapping
*
* @see #add(Object, Object)
* @param key the key
* @param value the object
* @return the previous mapped object or null
*/
public V put(K key, V value) {
return add(key, value);
}
/**
* adds a complete map to the cache
*
* @param map a map
* @see #put(Object, Object)
*/
public void putAll(Map<? extends K, ? extends V> map) {
Map<? extends K, ? extends V> smap = Collections.synchronizedMap(map);
Set<? extends K> set = smap.keySet();
synchronized (smap) {
for (Iterator<? extends K> i = set.iterator(); i.hasNext();) {
K k = i.next();
put(k, smap.get(k));
}
}
}
/**
* returns the key-set of the cache. CAUTION : if swap disk is enabled,
* all swapped keys can be returned. Unlike the usual keySet() returned
* by Map, this one IS NOT backed by the same map, further changes
* within the returned Collection WILL NOT BE REFLECTED. Changes made to
* the this cache ARE REFLECTED TO THE RETURNED COLLECTION.
*
* @return the key-set of the current cache , a TreeSet
* @see SortedMap#keySet()
*/
public Set<K> keySet() {
TreeSet<K> sortedKeys = new TreeSet<K>(comparator);
Set<KeyRegistry<K>> set = (swap) ? cacheDisk.keySet() : cache.keySet();
synchronized ((swap) ? cacheDisk : cache) {
for (Iterator<KeyRegistry<K>> i = set.iterator(); i.hasNext();) {
KeyRegistry<K> k = i.next();
if (k != null) {
sortedKeys.add(k.getKey());
}
}
}
keysSubLists.add(new WeakReference(sortedKeys, _cacheBack));
return (Set<K>) sortedKeys;
}
/**
* returns the cached objects collection. CAUTION : if swap disk is
* enabled, all swapped keys can be returned, Heap may be overflown with
* return instance-values. Unlike the usual values() returned by Map,
* this one IS NOT backed by the same map, further changes within the
* returned Collection WILL NOT BE REFLECTED. Changes made to the this
* cache ARE REFLECTED TO THE RETURNED COLLECTION.
*
* @return collection of objects that are currently living in the cache,
* a HashSet
* @see SortedMap#values()
*/
public Collection<V> values() {
HashSet<V> cached = new HashSet<V>();
Set<K> set = keySet();
synchronized (set) {
for (Iterator<K> i = set.iterator(); i.hasNext();) {
K k = i.next();
if (k != null) {
cached.add(get(k));
}
}
}
valuesSubLists.add(new WeakReference(cached, _cacheBack));
return (Collection<V>) cached;
}
/**
* the living entries of this SpritesCacheManager instance. CAUTION :
* the living entries only are returned unless swap disk is enabled !
* Unlike the usual entrySet() returned by Map, this one IS NOT backed
* by the same map, further changes within the returned Collection WILL
* NOT BE REFLECTED. Changes made to the this cache ARE REFLECTED TO THE
* RETURNED COLLECTION.
*
* @return the living entries of this SpritesCacheManager instance, a
* TreeMap
* @see SortedMap#entrySet()
*/
public Set<Map.Entry<K, V>> entrySet() {
SortedMap<K, V> m = new TreeMap<K, V>(comparator);
Set<K> set = keySet();
synchronized (set) {
for (Iterator<K> i = set.iterator(); i.hasNext();) {
K k = i.next();
if (k != null) {
m.put(k, get(k));
}
}
}
subMaps.add(new WeakReference(m, _cacheBack));
return m.entrySet();
}
private void endWrite(ObjectOutput out) throws IOException {
Set<Map.Entry<KeyRegistry<K>, File>> set = cacheDisk.entrySet();
synchronized (cacheDisk) {
for (Iterator<Map.Entry<KeyRegistry<K>, File>> files = set.iterator(); files.hasNext();) {
Map.Entry<KeyRegistry<K>, File> entry = files.next();
buffer(entry);
File f = entry.getValue();
RandomAccessFile raf = new RandomAccessFile(f, "r");
FileInputStream fis = new FileInputStream(raf.getFD());
BufferedInputStream bis = new BufferedInputStream(fis, FileHelper._SMALLBUFFFER_SIZE);
buffer(bis);
buffer(raf);
/* write swap : file name to recover swap < file length < file*/
out.writeObject(entry.getKey());
out.writeLong(f.length());
debug(f.length());
try {
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int readBytes = 0;
int writtenBytes = 0;
while ((readBytes = bis.read(b)) != -1) {
out.write(b, 0, readBytes);
writtenBytes += readBytes;
}
debug(writtenBytes);
bis.close();
raf.close();
} catch (Exception e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
} finally {
cleanup();
}
}
}
}
private static List fileCache = Collections.synchronizedList(new ArrayList<String>());
private void endRead(ObjectInput in) throws IOException, ClassNotFoundException {
cache = Collections.synchronizedMap(new WeakHashMap<KeyRegistry<K>, SoftValue<KeyRegistry<K>, V>>(listCapacity));
buffer(cache);
File file = null, tmpDir = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + cacheDisk_dir);
tmpDir.mkdirs();
int size = cacheDisk.size();
for (int i = 0; i < size; i++) {
boolean skipped = false;
RandomAccessFile raf = null;
ObjectOutputStream oos = null;
FileOutputStream fos = null;
KeyRegistry<K> key = (KeyRegistry<K>) in.readObject();
file = cacheDisk.get(key);
fileCache.add(file.getPath());
long len = in.readLong();
if (!file.exists()) {
if ((file = new File(tmpDir.getPath() + File.separator + file.getName())).isFile()) {
skipped = true;
}
}
if (file.length() == len) {
skipped = true;
} else {
skipped = false;
}
file.deleteOnExit();
debug("read " + file);
if (skipped) {
in.skipBytes((int) len);
} else {
raf = new RandomAccessFile(file, "rw");
raf.setLength(len);
fos = new FileOutputStream(raf.getFD());
BufferedOutputStream bos = new BufferedOutputStream(fos, FileHelper._SMALLBUFFFER_SIZE);
buffer(bos);
buffer(raf);
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int readBytes = 0;
int rBytes;
boolean fread = true;
do {
if (readBytes + b.length > len) {
in.readFully(b, 0, rBytes = ((int) len) - readBytes);
} else {
rBytes = in.read(b);
}
debug(".", false);
if (rBytes != -1) {
bos.write(b, 0, rBytes);
readBytes += rBytes;
} else {
fread = false;
}
if (readBytes >= len) {
fread = false;
}
} while (fread);
debug("bytes read: " + readBytes + " file length was: " + len);
bos.close();
raf.close();
cleanup();
}
cacheDisk.put(key, file);
}
debug("SpritesCacheManager read from DataStream!");
}
/**
* adds the specified listener to this SpritesCacheManager instance
*
* @param l the listener to add to this SpritesCacheManager instance
*/
public void addSpritesCacheListener(SpritesCacheListener l) {
listeners.add(l);
}
/**
* removes the specified listener from this SpritesCacheManager instace
*
* @param l the listener to remove from this SpritesCacheManager
* instance
* @see #addSpritesCacheListener(SpritesCacheListener)
*/
public void removeSpritesCacheListener(SpritesCacheListener l) {
listeners.remove(l);
}
/**
* notifies all the listeners of the specified write event
*
* @param event the event to notify the listeners
* @see SpritesCacheListener#WRITE_STARTED
* @see SpritesCacheListener#WRITE_ABORTED
* @see SpritesCacheListener#WRITE_COMPLETED
* @see SpritesCacheListener#WRITE_ERROR
* @see #notifyRead(int)
*/
protected void notifyWrite(int event) {
switch (event) {
case SpritesCacheListener.WRITE_STARTED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.writeStarted();
}
}
}
break;
case SpritesCacheListener.WRITE_ABORTED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.writeAborted();
}
}
}
break;
case SpritesCacheListener.WRITE_COMPLETED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.writeCompleted();
}
}
}
break;
case SpritesCacheListener.WRITE_ERROR:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.writeError(lastError);
}
}
}
break;
default:
}
}
/**
* notifies the listeners of the specified read event
*
* @param event the event
* @see SpritesCacheListener#READ_STARTED
* @see SpritesCacheListener#READ_ABORTED
* @see SpritesCacheListener#READ_COMPLETED
* @see SpritesCacheListener#READ_ERROR
* @see #notifyWrite(int)
*/
protected void notifyRead(int event) {
switch (event) {
case SpritesCacheListener.READ_STARTED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.readStarted();
}
}
}
break;
case SpritesCacheListener.READ_ABORTED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.readAborted();
}
}
}
break;
case SpritesCacheListener.READ_COMPLETED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.readCompleted();
}
}
}
break;
case SpritesCacheListener.READ_ERROR:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l != null) {
l.readError(lastError);
}
}
}
break;
default:
}
}
/**
* notifies the listeners of the specified event
*
* @param event
* @see #notifyRead(int)
* @see #notifyWrite(int)
*/
protected void notifyEvent(int event) {
switch (event) {
case SpritesCacheListener.READ_STARTED:
case SpritesCacheListener.READ_ABORTED:
case SpritesCacheListener.READ_COMPLETED:
case SpritesCacheListener.READ_ERROR:
notifyRead(event);
break;
case SpritesCacheListener.WRITE_STARTED:
case SpritesCacheListener.WRITE_ABORTED:
case SpritesCacheListener.WRITE_COMPLETED:
case SpritesCacheListener.WRITE_ERROR:
notifyWrite(event);
break;
case SpritesCacheListener.CAPACITY_EXTENDED:
synchronized (listeners) {
for (Iterator<SpritesCacheListener> i = listeners.iterator(); i.hasNext();) {
SpritesCacheListener l = i.next();
if (l instanceof SpritesCacheListener) {
l.capacityExtended(listCapacity);
}
}
}
break;
default:
}
}
/**
* builds a key-file or properitary file from a File instance that will
* be only readeable from another SpritesCacheManager instance. It can
* be compressed with the zip-deflater.
*
* @param key the key that will act as serial
* @param f the File of the data that has to be serialized
* @param fileDir the directory path name where the built key-file is
* gonna be stored
* @param keyFilename the file name of the built key-file
* @param compress dis/enables zip-deflater compression
* @return the resulting key-file instance
* @see #makeKeyFile(Serializable[], Serializable[], String, String,
* boolean)
*/
public static File makeKeyFile(Serializable key, File f, String fileDir, String keyFilename, boolean compress) {
try {
RandomAccessFile raf = new RandomAccessFile(f, "r");
FileInputStream fis = new FileInputStream(raf.getFD());
BufferedInputStream bis = new BufferedInputStream(fis, FileHelper._SMALLBUFFFER_SIZE);
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
SpritesCacheManager<Integer, byte[]> fileContents = new SpritesCacheManager<Integer, byte[]>((int) f.length());
fileContents.setSwapDiskEnabled(true);
int i = 0;
int readBytes;
while ((readBytes = bis.read(b)) != -1) {
byte[] rb = new byte[readBytes];
for (int j = 0; j < rb.length; j++) {
rb[j] = b[j];
}
fileContents.put(i++, rb);
}
bis.close();
raf.close();
return makeKeyFile(new Serializable[]{key}, new SpritesCacheManager[]{fileContents}, fileDir, keyFilename, compress);
} catch (FileNotFoundException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
} catch (IOException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
}
}
/**
* builds up from an array of Serializable instances a key-file or
* properitary file that can be only read by another SpritesCacheManager
* instance. It can be compressed by the zip-deflater, too.
*
* @param key the keys that will act as serials
* @param serialData the data that are to be serialized
* @param fileDir the directory path name where to write the resulting
* key-file
* @param keyFilename the file name of the resulting key-file
* @param compress dis/enables zip-deflater compression
* @return
* @throws IndexOutOfBoundsException
* @see #makeKeyFile(Serializable, File, String, String, boolean)
*/
public static File makeKeyFile(Serializable[] key, Serializable[] serialData, String fileDir, String keyFilename, boolean compress) throws IndexOutOfBoundsException {
if (key.length != serialData.length) {
throw new IndexOutOfBoundsException("Keys and serial datas must be of the same length. there are " + key.length + " keys and " + serialData.length + " serial datas");
}
SpritesCacheManager<Serializable, Serializable> spm = new SpritesCacheManager<Serializable, Serializable>();
spm.setSwapDiskEnabled(true);
spm.setCompressionEnabled(compress);
for (int i = 0; i < key.length; i++) {
spm.put(key[i], serialData[i]);
}
new File(fileDir).mkdirs();
spm.cacheDisk_dir = fileDir;
ObjectOutputStream out;
FileOutputStream fos;
BufferedOutputStream bos;
try {
File keyFile = new File(fileDir + File.separator + keyFilename), temp = FileHelper._createTempFile("spKey_", _TMPDIRECTORY, true);
RandomAccessFile raf = new RandomAccessFile(temp, "rw");
out = new ObjectOutputStream(bos = new BufferedOutputStream(fos = new FileOutputStream(raf.getFD()), FileHelper._SMALLBUFFFER_SIZE));
out.writeObject(spm);
out.flush();
out.close();
bos.close();
raf.close();
raf = new RandomAccessFile(temp, "r");
RandomAccessFile rafCopy = new RandomAccessFile(keyFile, "rw");
rafCopy.setLength(temp.length());
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int readBytes = 0;
while ((readBytes = raf.read(b)) != -1) {
rafCopy.write(b, 0, readBytes);
}
rafCopy.close();
raf.close();
temp.delete();
return keyFile;
} catch (FileNotFoundException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
} catch (IOException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
}
}
/**
* extracts the file from a key-file that has been built with the
* {@link #makeKeyFile(Serializable, File, String, String, boolean) makeKeyFile}
* methods.
*
* @param f the File that contains the serialized data
* @param key the key serial to open the lock
* @param extractFilename the file name of the extracted File
* @return the extracted File
* @see #extractKeyData(File, Serializable)
*/
public static File extractKeyFile(File f, Serializable key, String extractFilename) {
try {
SpritesCacheManager<Integer, byte[]> serial = (SpritesCacheManager<Integer, byte[]>) extractKeyData(f, key);
if (serial == null) {
return null;
}
File xF = new File(extractFilename), temp = FileHelper._createTempFile("keyFile_", _TMPDIRECTORY, true);
RandomAccessFile raf = new RandomAccessFile(temp, "rw");
FileOutputStream fos = new FileOutputStream(raf.getFD());
BufferedOutputStream bos = new BufferedOutputStream(fos, FileHelper._SMALLBUFFFER_SIZE);
for (int i = 0; i < serial.getSwapMap().size(); i++) {
bos.write(serial.readSwap(new KeyRegistry(i)));
}
bos.close();
raf.close();
raf = new RandomAccessFile(temp, "r");
RandomAccessFile rafCopy = new RandomAccessFile(xF, "rw");
rafCopy.setLength(temp.length());
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int readBytes = 0;
while ((readBytes = raf.read(b)) != -1) {
rafCopy.write(b, 0, readBytes);
}
rafCopy.close();
raf.close();
temp.delete();
return xF;
} catch (FileNotFoundException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
} catch (IOException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
}
}
/**
* extracts serialized data from a key-file built with
* {@link #makeKeyFile(Serializable[], Serializable[], String, String, boolean) makeKeyFile}
* method.
*
* @param f the file that contains the serialized data
* @param key the key to extract the associated serialized data
* @return the extracted data
* @see #extractKeyFile(File, Serializable, String)
*/
public static Serializable extractKeyData(File f, Serializable key) {
ObjectInputStream in;
FileInputStream fin;
BufferedInputStream bin;
try {
RandomAccessFile raf = new RandomAccessFile(f, "r");
if (!isExtClassLoadIO()) {
in = new ObjectInputStream(bin = new BufferedInputStream(fin = new FileInputStream(raf.getFD()), FileHelper._SMALLBUFFFER_SIZE));
} else {
in = ExtensionsClassLoader.getInstance().new ObjectInputStream(bin = new BufferedInputStream(fin = new FileInputStream(raf.getFD()), FileHelper._SMALLBUFFFER_SIZE));
}
SpritesCacheManager<Serializable, Serializable> spm;
spm = (SpritesCacheManager<Serializable, Serializable>) in.readObject();
in.close();
bin.close();
raf.close();
Serializable serialData = null;
serialData = spm.readSwap(new KeyRegistry(key));
return serialData;
} catch (FileNotFoundException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
} catch (IOException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
} catch (ClassNotFoundException ex) {
if (DebugMap._getInstance().isDebuggerEnabled(SpritesCacheManager.class)) {
ex.printStackTrace();
}
return null;
}
}
/**
* the Comparator instance that specifies the order in which the keys
* are to be sorted. CAUTION : the Comparator instance doesn't get
* serialized and must be reset up after a deserialization process.
*
* @see #setComparator(Comparator)
*/
public transient Comparator<? super K> comparator = null;
/**
* sets up the Comparator instance that specifies the order in which the
* keys are to be sorted.
*
* @param c the Comparator instance that specifies the order in which
* the keys are to be sorted
*/
public void setComparator(Comparator<? super K> c) {
comparator = c;
}
/**
* returns the Comparator instance that sorts the keys for this
* SpritesCacheManager {@link #keySet() Collections views}
*
* @return the Comparator instance that sorts the keys for this
* SpritesCacheManager instance Collections views
* @see #keySet()
* @see #setComparator(Comparator)
*/
public Comparator<? super K> getComparator() {
return comparator();
}
/**
* returns the Comparator instance that sorts the keys for this
* SpritesCacheManager instance
*
* @return the Comparator instance that sorts the keys for this
* SpritesCacheManager instance
* @see #getComparator()
*/
public Comparator<? super K> comparator() {
return comparator;
}
/**
* returns a Collections view of the mapped living entries map
*
* @return a Collections view of the mapped living entries map
* @param fromKey the key the start the sub-map from, inclusive
* @param toKey the key to end the sub-map with, exclusive
* @see #entrySet()
*/
public SortedMap<K, V> subMap(K fromKey, K toKey) {
Set<Map.Entry<K, V>> entries = entrySet();
SortedMap<K, V> sortedMap = new TreeMap<K, V>(comparator);
synchronized (entries) {
for (Iterator<Map.Entry<K, V>> i = entries.iterator(); i.hasNext();) {
Map.Entry<K, V> entry = i.next();
sortedMap.put(entry.getKey(), entry.getValue());
}
}
subMaps.add(new WeakReference(sortedMap, _cacheBack));
return sortedMap.subMap(fromKey, toKey);
}
/**
* returns a Collections view of the head of the mapped living entries
* map
*
* @return a Collections view of the head of the mapped living entries
* map
* @param headKey the key to end the sub-map with, exclusive. all keys
* stricly less than the specified key are grabbed.
* @see #entrySet()
*/
public SortedMap<K, V> headMap(K headKey) {
Set<Map.Entry<K, V>> entries = entrySet();
SortedMap<K, V> sortedMap = new TreeMap<K, V>(comparator);
synchronized (entries) {
for (Iterator<Map.Entry<K, V>> i = entries.iterator(); i.hasNext();) {
Map.Entry<K, V> entry = i.next();
sortedMap.put(entry.getKey(), entry.getValue());
}
}
subMaps.add(new WeakReference(sortedMap, _cacheBack));
return sortedMap.headMap(headKey);
}
/**
* returns a Collections view of the tail of the mapped living entries
* map
*
* @param tailKey the key to start the sub-map from, inclusive. all keys
* greater than or equal to the specified key are grabbed.
* @return a Collections view of the tail of the mapped living entries
* map
* @see #entrySet()
*/
public SortedMap<K, V> tailMap(K tailKey) {
Set<Map.Entry<K, V>> entries = entrySet();
SortedMap<K, V> sortedMap = new TreeMap<K, V>(comparator);
synchronized (entries) {
for (Iterator<Map.Entry<K, V>> i = entries.iterator(); i.hasNext();) {
Map.Entry<K, V> entry = i.next();
sortedMap.put(entry.getKey(), entry.getValue());
}
}
subMaps.add(new WeakReference(sortedMap, _cacheBack));
return sortedMap.tailMap(tailKey);
}
/**
* returns the first key in this SpritesCacheManager instance
*
* @return the first key int this SpritesCacheManager
* @see #keySet()
*/
public K firstKey() {
TreeSet<K> keys = ((TreeSet<K>) keySet());
return (keys.isEmpty()) ? null : keys.first();
}
/**
* sets up the list capacity of this SpritesCacheManager instance. a
* {@link SpritesCacheListener#CAPACITY_EXTENDED capacity has been extended}
* event is sent to the listeners.
*
* @param listCapacity the new list capacity
*/
public void setListCapacity(int listCapacity) {
this.listCapacity = listCapacity;
notifyEvent(SpritesCacheListener.CAPACITY_EXTENDED);
}
/**
* returns the current list capacity
*
* @return the current list capacity
*/
public int getListCapacity() {
return listCapacity;
}
/**
* returns the last key of this SpritesCacheManager instance
*
* @return the las key of this SpritesCacheManager instance
* @see #keySet()
*/
public K lastKey() {
TreeSet<K> keys = ((TreeSet<K>) keySet());
return (keys.isEmpty()) ? null : keys.last();
}
public void writeExternal(ObjectOutput out) throws IOException {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
out.writeObject(cacheDisk);
out.writeUTF(cacheDisk_dir);
out.writeUTF(cacheDisk_ext);
out.writeUTF(cacheDisk_prefix);
out.writeBoolean(compress);
out.writeLong(hash);
out.writeInt(initialListCapacity);
if (lastError != null) {
out.writeBoolean(true);
out.writeObject(lastError);
} else {
out.writeBoolean(false);
}
out.writeInt(listCapacity);
out.writeBoolean(swap);
out.writeLong(swap_memory_usage);
endWrite(out);
out.flush();
Thread.currentThread().setPriority(pty);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
cacheDisk = (SortedMap<KeyRegistry<K>, File>) in.readObject();
cacheDisk_dir = in.readUTF();
cacheDisk_ext = in.readUTF();
cacheDisk_prefix = in.readUTF();
compress = in.readBoolean();
hash = in.readLong();
initialListCapacity = in.readInt();
if (in.readBoolean()) {
lastError = (Exception) in.readObject();
}
listCapacity = in.readInt();
swap = in.readBoolean();
swap_memory_usage = in.readLong();
endRead(in);
Thread.currentThread().setPriority(pty);
}
/**
* We define our own subclass of WeakReference which contains not only
* the value but also the key to make it easier to find the entry in the
* HashMap after it's been garbage collected.
*
* @author www.b23prodtm.info
*/
class SoftValue<L, V> extends WeakReference<V> {
final L key;
final boolean enqueueClear;
/**
* @param enqueueClear forward referent to cleanup() (a "false"
* value is the default behaviour of Soft- and WeakReference)
*/
SoftValue(V k, L key, boolean enqueueClear) {
super(k, _cacheBack);
this.key = key;
this.enqueueClear = enqueueClear;
}
/**
* Parameter enqueuing is set to true, so that it by default
* comes to return the referenced object (=referent) before to
* clear it.
*/
public SoftValue(V k, L key) {
this(k, key, true);
}
/**
* to allow proper cleanup through
* {@linkplain WeakReference#get()} before to clear
*/
@Override
public void clear() {
if (enqueueClear) {
Object o = get();
if (o != null) {
SoftValue sClear = new SoftValue(o, key, false);
sClear.enqueue();
}
}
super.clear();
}
}
/**
* A key-registry is necessary for keys that are known as primitive,
* like int, long and others to be kept alive in the
* {@link WeakHashMap WeakHashMap}.
*/
static class KeyRegistry<K> implements Comparable, Externalizable {
/**
* the key
*/
private K key;
/**
* FOR EXTERNALIZED PURPOSE ONLY (null public contructor)
* creates a new immutable instance
*
* @param key the key of this KeyRegistry instance
*/
public KeyRegistry() {
key = null;
}
/**
* This is the public constructor. creates a new immutable
* instance
*
* @param key the key of this KeyRegistry instance
*/
public KeyRegistry(K key) throws NullPointerException {
this.key = key;
if (key == null) {
throw new NullPointerException(getClass().getName() + " only non-null keys are accepted");
}
}
/**
* returns the key of this KeyRegistry instance
*
* @return the key of this KeyRegistry instance
*/
public K getKey() {
return key;
}
/**
* returns the hash-code of this KeyRegistry instance, actually
* the hash-code of the key registred object.
*
* @return the hash-code of this KeyRegistry instance
*/
public int hashCode() {
return key.hashCode();
}
/**
* returns true or false, whether the specified Object is equal
* or not, resp.
*
* @return true or false whther the specified Object is equal or
* not, resp.
* @see #hashCode()
*/
public boolean equals(Object o) {
return o == null ? false : hashCode() == o.hashCode();
}
/**
* returns the natural order of the key if it is
* {@link Comparable comparable}, 0 if it is equal or -1 if the
* specified object is null or not a KeyRegistry.
*
* @param o the Object to compare this KeyRegistry instance to.
* @return the natural order of the stored key for this
* KeyRegistry compared to the specified Object
*/
public int compareTo(Object o) {
return equals(o) ? 0 : (o instanceof KeyRegistry && key instanceof Comparable ? ((Comparable) key).compareTo(((KeyRegistry) o).getKey()) : (o == null ? 1 : Integer.valueOf(hashCode()).compareTo(Integer.valueOf(o.hashCode()))));
}
/**
*
*/
public String toString() {
return key.toString();
}
/**
* the key is serialized to the output
*/
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(key);
}
/**
* the key is read from the input
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
key = (K) in.readObject();
}
}
public boolean isMultiThreadingEnabled() {
return _multiThreading;
}
public void setMultiThreadingEnabled(boolean b) {
_multiThreading = b;
}
}