com.jidesoft.utils.CacheMap Maven / Gradle / Ivy
/*
* @(#) CacheMap.java
*
* Copyright 2002 - 2003 JIDE Software. All rights reserved.
*/
package com.jidesoft.utils;
import java.util.*;
/**
* CacheMap
is a two-level HashMap
. It uses Class as the key and you can map the key to an
* object and a context as a pair. We use context because we want to register multiple objects with the same Class.
* {@link #register(Class, Object, Object)} is the method to register a new entry. {@link #getRegisteredObject(Class,
* Object)} will allow you to look up the object by specifying the Class and the context.
*/
public class CacheMap {
private HashMap, Cache> _cache = new HashMap, Cache>();
private K _defaultContext; // used for fallback lookup.
/**
* Constructs a CacheMap
.
*
* @param defaultContext the default context.
*/
public CacheMap(K defaultContext) {
_defaultContext = defaultContext;
}
static class Cache extends HashMap {
private static final long serialVersionUID = 7764545350468551102L;
public T getObject(K context) {
return get(context);
}
public void setObject(K context, T object) {
if (object == null) {
remove(context);
}
else {
put(context, object);
}
}
}
protected Cache getCache(Class> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("Clazz cannot be null");
}
return _cache.get(clazz);
}
/**
* Gets the secondary keys that are registered with the class in CacheMap.
*
* @param clazz the class
* @param a the array to receive the keys.
*
* @return the secondary keys.
*/
public K[] getKeys(Class> clazz, K[] a) {
Cache cache = getCache(clazz);
if (cache != null) {
Set set = cache.keySet();
return set.toArray(a);
}
else {
return a;
}
}
protected Cache initCache(Class> clazz) {
Cache cache = getCache(clazz);
if (cache != null) {
return cache;
}
else {
cache = new Cache();
_cache.put(clazz, cache);
return cache;
}
}
/**
* Registers an object with the specified clazz and object.
*
* @param clazz the class which is used as the key.
* @param object the object, or the value of the mapping
* @param context the secondary key. It is used to register multiple objects to the same primary key (the clazz
* parameter in this case).
*/
public void register(Class> clazz, T object, K context) {
if (clazz == null) {
throw new IllegalArgumentException("Parameter clazz cannot be null");
}
Cache cache = initCache(clazz);
cache.setObject(context, object);
fireRegistrationChanged(new RegistrationEvent(this, RegistrationEvent.REGISTRATION_ADDED, object, clazz, context));
}
/**
* Unregisters the object associated with the specified class and context.
*
* @param clazz the class
* @param context the context
*/
public void unregister(Class> clazz, K context) {
Cache cache = getCache(clazz);
if (cache != null) {
Object object = cache.getObject(context);
cache.setObject(context, null);
fireRegistrationChanged(new RegistrationEvent(this, RegistrationEvent.REGISTRATION_REMOVED, object, clazz, context));
if (cache.size() == 0) {
_cache.remove(clazz);
}
}
}
/**
* Gets registered object from CacheMap. The algorithm used to look up is
1. First check for exact match with
* clazz and context.
2. If didn't find, look for interfaces that clazz implements using the exact context.
* 3. If still didn't find, look for super class of clazz using the exact context.
4. If still didn't find,
* using the exact clazz with default context.
5. If still didn't find, return null.
If found a match in
* step 1, 2, 3 or 4, it will return the registered object immediately.
*
* @param clazz the class which is used as the primary key.
* @param context the context which is used as the secondary key. This parameter could be null in which case the
* default context is used.
*
* @return registered object the object associated with the class and the context.
*/
public T getRegisteredObject(Class> clazz, K context) {
if (clazz == null) {
return null;
}
Cache cache = getCache(clazz);
if (cache == null || !cache.containsKey(context)) {
List> classesToSearch = new ArrayList>();
classesToSearch.add(clazz);
if (TypeUtils.isPrimitive(clazz)) {
classesToSearch.add(TypeUtils.convertPrimitiveToWrapperType(clazz));
}
else if (TypeUtils.isPrimitiveWrapper(clazz)) {
classesToSearch.add(TypeUtils.convertWrapperToPrimitiveType(clazz));
}
// Direct super interfaces, recursively
Class>[] interfaces = clazz.getInterfaces();
classesToSearch.addAll(Arrays.asList(interfaces));
Class> superClass = clazz;
// Direct super class, recursively
while (!superClass.isInterface()) {
superClass = superClass.getSuperclass();
if (superClass != null) {
classesToSearch.add(superClass);
interfaces = superClass.getInterfaces();
classesToSearch.addAll(Arrays.asList(interfaces));
}
else {
break;
}
}
List> interfacesToSearch = new ArrayList>();
for (Class> aClass : classesToSearch) {
if (aClass.isInterface()) {
addInterface(aClass, interfacesToSearch, classesToSearch);
}
}
classesToSearch.addAll(interfacesToSearch);
classesToSearch.remove(Object.class);
classesToSearch.add(Object.class); // use Object as the last default fallback.
// search to match context first
for (Class> c : classesToSearch) {
Cache cacheForClass = getCache(c);
if (cacheForClass != null) {
T object = cacheForClass.getObject(context);
if (object != null) {
return object;
}
}
}
// fall back to default context
if (!_defaultContext.equals(context)) {
for (Class> c : classesToSearch) {
Cache cacheForClass = getCache(c);
if (cacheForClass != null) {
T object = cacheForClass.getObject(_defaultContext);
if (object != null) {
return object;
}
}
}
}
}
if (cache != null) {
T object = cache.getObject(context);
if (object == null && !_defaultContext.equals(context)) {
return getRegisteredObject(clazz, _defaultContext);
}
if (object != null) {
return object;
}
}
return null;
}
private void addInterface(Class> anInterface, List> interfacesToSearch, List> classesToSearch) {
if (anInterface != null) {
Class>[] interfaces = anInterface.getInterfaces();
for (Class> superInterface : interfaces) {
if (!classesToSearch.contains(superInterface)) {
interfacesToSearch.add(superInterface);
addInterface(superInterface, interfacesToSearch, classesToSearch);
}
}
}
}
/**
* Gets the exact match registered object. Different from {@link #getRegisteredObject(Class, Object)} which will try
* different context and super classes and interfaces to find match. This method will do an exact match.
*
* @param clazz the class which is used as the primary key.
* @param context the context which is used as the secondary key. This parameter could be null in which case the
* default context is used.
*
* @return registered object the object associated with the class and the context.
*/
public T getMatchRegisteredObject(Class> clazz, K context) {
if (clazz == null) {
return null;
}
if (context == null) {
context = _defaultContext;
}
Cache cache = getCache(clazz);
if (cache != null) {
T object = cache.getObject(context);
if (object != null) {
return object;
}
}
return null;
}
public List getValues() {
List list = new ArrayList();
Collection> col = _cache.values();
for (Cache o : col) {
Collection col2 = o.values();
for (T o2 : col2) {
if (!list.contains(o2)) {
list.add(o2);
}
}
}
return list;
}
/**
* Remove all registrations for the designated class.
*
* @param clazz the class
*/
@SuppressWarnings("unchecked")
public void remove(Class> clazz) {
Cache cache = getCache(clazz);
if (cache != null) {
Object[] keys = cache.keySet().toArray();
for (Object context : keys) {
Object object = cache.getObject((K) context);
cache.setObject((K) context, null);
fireRegistrationChanged(new RegistrationEvent(this, RegistrationEvent.REGISTRATION_REMOVED, object, clazz, context));
}
}
_cache.remove(clazz);
}
public void clear() {
_cache.clear();
fireRegistrationChanged(new RegistrationEvent(this, RegistrationEvent.REGISTRATION_CLEARED));
}
/**
* List of listeners
*/
protected List listenerList = new ArrayList();
/**
* Adds a listener to the list that's notified each time a change to the registration occurs.
*
* @param l the RegistrationListener
*/
public void addRegistrationListener(RegistrationListener l) {
listenerList.add(l);
}
/**
* Removes a listener from the list that's notified each time a change to the registration occurs.
*
* @param l the RegistrationListener
*/
public void removeRegistrationListener(RegistrationListener l) {
listenerList.remove(l);
}
/**
* Returns an array of all the registration listeners registered on this registration.
*
* @return all of this registration's RegistrationListener
s or an empty array if no registration
* listeners are currently registered
*
* @see #addRegistrationListener
* @see #removeRegistrationListener
*/
public RegistrationListener[] getRegistrationListeners() {
return listenerList.toArray(new RegistrationListener[listenerList.size()]);
}
/**
* Forwards the given notification event to all RegistrationListeners
that registered themselves as
* listeners for this table model.
*
* @param e the event to be forwarded
*
* @see #addRegistrationListener
* @see RegistrationEvent
*/
public void fireRegistrationChanged(RegistrationEvent e) {
// Guaranteed to return a non-null array
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listenerList.size() - 2; i >= 0; i -= 2) {
listenerList.get(i + 1).registrationChanged(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy