// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.are.viewer; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; /** * A global storage class that caches data objects of supported types associated with a unique key. */ public class SharedResourceCache { // Identifies the type of cache object to retrieve public static enum Type { ICON, ANIMATION/*, Creature*/ } private static EnumMap<Type, HashMap<Object, DataWrapper>> tables = new EnumMap<Type, HashMap<Object, DataWrapper>>(Type.class); static { tables.put(Type.ICON, new HashMap<Object, DataWrapper>()); tables.put(Type.ANIMATION, new HashMap<Object, DataWrapper>()); // tables.put(Type.Creature, new HashMap<Object, DataWrapper>()); } /** * Generates a simple key from the hash code of the specified object. * @param o The object to create a key for. * @return A key generated from the hash code of the specified object, or 0 on {@code null}. */ public static String createKey(Object o) { return String.format("%1$08x", (o != null) ? o.hashCode() : 0); } /** * Adds the data object to the cache. * @param key A unique key for identifying the data object. * @param data The data object to store. * @return {@code true} if a new entry has been created for the data object. * {@code false} if the entry already exists in the cache. * @exception NullPointerException if key is {@code null}. */ public static synchronized boolean add(Type type, Object key, Object data) { if (type == null) { throw new NullPointerException("type is null"); } if (key == null) { throw new NullPointerException("key is null"); } if (tables.get(type).containsKey(key)) { // add reference only tables.get(type).get(key).incRefCount(); return false; } else { // add new entry tables.get(type).put(key, new DataWrapper(data)); return true; } } /** * Adds another reference to the cache entry specified by the key. * @param key A unique key for identifying a data object. * @return {@code true} if the key exists, {@code false} otherwise. * @exception NullPointerException if key is {@code null}. */ public static synchronized boolean add(Type type, Object key) { if (type == null) { throw new NullPointerException("type is null"); } if (key == null) { throw new NullPointerException("key is null"); } if (tables.get(type).containsKey(key)) { tables.get(type).get(key).incRefCount(); return true; } else { return false; } } /** * Removes one reference from the entry identified by the specified key. If the reference count for * the entry is 0, the whole entry will be removed from cache. * @param key The key identifying the data object. * @return {@code true} if the entry has been removed completely, {@code false} otherwise. * @exception NullPointerException if key is {@code null}. */ public static synchronized boolean remove(Type type, Object key) { if (type == null) { throw new NullPointerException("type is null"); } if (key == null) { throw new NullPointerException("key is null"); } if (tables.get(type).containsKey(key)) { DataWrapper dw = tables.get(type).get(key); dw.decRefCount(); if (!dw.isReferenced()) { tables.get(type).remove(key); return true; } } return false; } /** * Returns the data object identified by the specified key. * @param key The key identifying the data object. * @return The data object identified by the specified key. * @exception NullPointerException if key is {@code null}. */ public static Object get(Type type, Object key) { if (type == null) { throw new NullPointerException("type is null"); } if (key == null) { throw new NullPointerException("key is null"); } if (tables.get(type).containsKey(key)) { return tables.get(type).get(key).getData(); } return null; } /** * Returns whether a cached data object of the specified key exists. * @param key The key identifying a cached data object. * @return {@code true} if a cached entry exists, {@code false} otherwise. * @exception NullPointerException if key is {@code null}. */ public static boolean contains(Type type, Object key) { if (type == null) { throw new NullPointerException("type is null"); } if (key == null) { throw new NullPointerException("key is null"); } return tables.get(type).containsKey(key); } /** * Attempts to find the first key that is associated with the specified data object. * @param data The data object. * @return The first key that is associated with the specified data object, * or {@code null} if no key has been found. */ public static Object getKey(Type type, Object data) { if (type == null) { throw new NullPointerException("type is null"); } Iterator<Object> iter = tables.get(type).keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); DataWrapper dw = tables.get(type).get(key); if ((dw.getData() == null && dw.getData() == data) || (dw.getData() != null && dw.getData().equals(data))) { return key; } } return null; } /** * Returns the number of references for the data object identified by the specified key. * @param key The key identifying the cached data object. * @return Number of references for the specified data object. * @exception NullPointerException if key is {@code null}. */ public static int getReferenceCount(Type type, Object key) { if (type == null) { throw new NullPointerException("type is null"); } if (key == null) { throw new NullPointerException("key is null"); } if (key != null) { if (tables.get(type).containsKey(key)) { return tables.get(type).get(key).getRefCount(); } } return 0; } /** Not needed. Contains only static methods and data. */ private SharedResourceCache() {} //----------------------------- INNER CLASSES ----------------------------- /** Wrapper for data objects that supports reference counting. */ private static class DataWrapper { private Object data; private int refCount; /** * Initializes a new data wrapper object. Reference counter is set to 1. * @param data The associated data object. */ public DataWrapper(Object data) { this.data = data; refCount = 1; } /** * Returns the data object. */ public Object getData() { return data; } /** * Returns the current reference counter value. */ public int getRefCount() { return refCount; } /** * Increases the reference counter by 1. */ public synchronized void incRefCount() { refCount++; } /** * Decreases the reference counter by 1 and returns whether the data is still referenced by any * external object. * @return {@code true} if the data is referenced, {@code false} otherwise. */ public synchronized boolean decRefCount() { if (refCount > 0) { refCount--; } return (refCount > 0); } /** * Returns whether the data is externally referenced. * @return {@code true} if the data is referenced, {@code false} otherwise. */ public boolean isReferenced() { return (refCount > 0); } } }