Package net.sf.jiga.xtended.kernel

Source Code of net.sf.jiga.xtended.kernel.SpritesCacheManager$SoftValue

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;
        }
}
TOP

Related Classes of net.sf.jiga.xtended.kernel.SpritesCacheManager$SoftValue

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.