/* * Copyright (c) 2008-2014 MongoDB, 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.List; import java.util.Map; /** * <p>Maps Class objects to values. A ClassMap is different from a regular Map in that {@code get(clazz)} does not only look to see if * {@code clazz} is a key in the Map, but also walks the up superclass and interface graph of {@code clazz} to find matches. Derived matches * of this sort are then cached in the registry so that matches are faster on future gets.</p> * * <p>This is a very useful class for Class based registries.</p> * * <p>Example:</p> * <pre>{@code * ClassMap<String> m = new ClassMap<String>(); * m.put(Animal.class, "Animal"); * m.put(Fox.class, "Fox"); * m.get(Fox.class) --> "Fox" * m.get(Dog.class) --> "Animal" * } </pre> * * (assuming Dog.class < Animal.class) * * @param <T> the type of the value in this map */ public class ClassMap<T> { /** * Helper method that walks superclass and interface graph, superclasses first, then interfaces, to compute an ancestry list. Super * types are visited left to right. Duplicates are removed such that no Class will appear in the list before one of its subtypes. * * @param clazz the class to get the ancestors for * @param <T> the type of the class modeled by this {@code Class} object. * @return a list of all the super classes of {@code clazz}, starting with the class, and ending with {@code java.lang.Object}. */ public static <T> List<Class<?>> getAncestry(final Class<T> clazz) { return ClassAncestry.getAncestry(clazz); } private final class ComputeFunction implements Function<Class<?>, T> { @Override public T apply(final Class<?> a) { for (final Class<?> cls : getAncestry(a)) { T result = map.get(cls); if (result != null) { return result; } } return null; } } private final Map<Class<?>, T> map = CopyOnWriteMap.newHashMap(); private final Map<Class<?>, T> cache = ComputingMap.create(new ComputeFunction()); /** * Gets the value associated with either this Class or a superclass of this class. If fetching for a super class, it fetches the value * for the closest superclass. Returns null if the given class and none of its superclasses are in the map. * * @param key a {@code Class} to get the value for * @return the value for either this class or its nearest superclass */ public T get(final Object key) { return cache.get(key); } /** * As per {@code java.util.Map}, associates the specified value with the specified key in this map. If the map previously contained a * mapping for the key, the old value is replaced by the specified value. * * @param key a {@code Class} key * @param value the value for this class * @return the previous value associated with {@code key}, or null if there was no mapping for key. * @see java.util.Map#put(Object, Object) */ public T put(final Class<?> key, final T value) { try { return map.put(key, value); } finally { cache.clear(); } } /** * As per {@code java.util.Map}, removes the mapping for a key from this map if it is present * * @param key a {@code Class} key * @return the previous value associated with {@code key}, or null if there was no mapping for key. * @see java.util.Map#remove(Object) */ public T remove(final Object key) { try { return map.remove(key); } finally { cache.clear(); } } /** * As per {@code java.util.Map}, removes all of the mappings from this map (optional operation). * * @see java.util.Map#clear() */ public void clear() { map.clear(); cache.clear(); } /** * As per {@code java.util.Map}, returns the number of key-value mappings in this map. This will only return the number of keys * explicitly added to the map, not any cached hierarchy keys. * * @return the size of this map * @see java.util.Map#size() */ public int size() { return map.size(); } /** * As per {@code java.util.Map}, returns {@code true} if this map contains no key-value mappings. * * @return true if there are no values in the map * @see java.util.Map#isEmpty() */ public boolean isEmpty() { return map.isEmpty(); } }