// ClassMap.java /** * Copyright (C) 2008 10gen Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.bson.util; import java.util.*; /** * Maps Class objects to values. A ClassMap is different from a regular Map * in that get(c) does not only look to see if 'c' is a key in the * Map, but also walks the up superclass and interface graph of 'c' * to find matches. Derived matches of this sort are then "cached" in the * registry so that matches are faster on future gets. * * This is a very useful class for Class based registries. * * Example: * * ClassMap<String> m = new ClassMap<String>(); * m.put(Animal.class, "Animal"); * m.put(Fox.class, "Fox"); * System.out.println(m.get(Fox.class)) // --> "Fox" * System.out.println(m.get(Dog.class)) // --> "Animal" * * (assuming Dog.class < Animal.class) * */ public class ClassMap<T> implements Map <Class, T> { /** * internalMap */ protected Map<Class, T> getInternalMap(){ return(_internalMap); } private void setInternalMap(Map m){ _internalMap = m; } /** * cache */ protected Map<Class, T> getCache(){ return(_cache); } private void setCache(Map m){ _cache = m; } /** * size */ public synchronized int size(){ return(getCache().size()); } /** * isEmpty */ public synchronized boolean isEmpty(){ return(getCache().isEmpty()); } /** * containsKey */ public synchronized boolean containsKey(Object key){ return(get(key) != null); } /** * cacheContainsKey */ protected synchronized boolean cacheContainsKey(Object key){ return(getCache().containsKey(key)); } /** * containsValue */ public synchronized boolean containsValue(Object object){ return(getCache().containsValue(object)); } /** * get */ public synchronized T get(Object key){ Class c = (Class)key; Map<Class, T> cache = getCache(); if (cache.containsKey(c)){ return(cache.get(c)); } T result = computeValue(c); cache.put(c, result); // will also cache failures return(result); } /** * put */ public synchronized T put(Class key, T value){ T result = getInternalMap().put(key, value); initCache(); return(result); } /** * remove */ public synchronized T remove(Object object){ T result = getInternalMap().remove(object); initCache(); return(result); } /** * putAll */ public synchronized void putAll(Map map){ getInternalMap().putAll(map); initCache(); } /** * clear */ public synchronized void clear(){ getInternalMap().clear(); initCache(); } /** * keySet */ public synchronized Set<Class> keySet(){ return(getCache().keySet()); } /** * values */ public synchronized Collection<T> values(){ return(getCache().values()); } /** * entrySet */ public synchronized Set<Map.Entry<Class, T>> entrySet(){ return(getCache().entrySet()); } /** * equals */ public boolean equals(Object object){ try { ClassMap that = (ClassMap)object; return(getInternalMap().equals(that.getInternalMap())); } catch (ClassCastException cce) { return(false); } } /** * hashCode */ public int hashCode(){ return(getInternalMap().hashCode()); } /** * computeValue */ private T computeValue(Class key){ List<Class> ancestry = getAncestry(key); Map<Class, T> map = getCache(); for (Class c : ancestry) { if (map.containsKey(c)) { T value = map.get(c); return(value); } } return(null); } /** * initCache */ protected void initCache(){ Map cache = getCache(); cache.clear(); cache.putAll(getInternalMap()); } /** * toString */ public String toString() { return(getCache().toString()); } /** * getAncestry * * Walks superclass and interface graph, superclasses first, then * interfaces, to compute an ancestry list. Supertypes are visited * left to right. Duplicates are removed such that no Class will * appear in the list before one of its subtypes. * * Does not need to be synchronized, races are harmless * as the Class graph does not change at runtime. */ public static List<Class> getAncestry(Class c){ List<Class> result = null; Map<Class, List<Class>> cache = getClassAncestryCache(); List<Class> cachedResult = cache.get(c); if (cachedResult != null) { result = cachedResult; } else { result = new ArrayList<Class>(); List<Class> ancestry = computeAncestry(c); int size = ancestry.size(); for (int i = 0; i < size; i++) { result.add(ancestry.get((size - i) - 1)); } cache.put(c, result); } return(result); } /** * computeAncestry */ private static List<Class> computeAncestry(Class c){ List<Class> result = new ArrayList<Class>(); result.add(Object.class); computeAncestry(c, result); return(result); } private static void computeAncestry(Class c, List result){ if ((c == null) || (c == Object.class)){ return; } // first interfaces (looks backwards but is not) Class[] interfaces = c.getInterfaces(); for (int i = interfaces.length - 1; i >= 0; i--){ computeAncestry(interfaces[i], result); } // next superclass computeAncestry(c.getSuperclass(), result); if (!result.contains(c)) result.add(c); } /** * classAncestryCache */ private static Map getClassAncestryCache(){ return(_ancestryCache); } private static void setClassAncestryCache(Map m){ _ancestryCache = m; } /** * private members */ private Map<Class, T> _internalMap = new HashMap<Class, T>(); private Map<Class, T> _cache = new HashMap<Class, T>(); private static Map<Class, List<Class>> _ancestryCache = new HashMap<Class, List<Class>>(); }