/* Copyright 2009 Ben Gunter * * 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 net.sourceforge.stripes.util; import java.lang.annotation.Annotation; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.sourceforge.stripes.controller.ObjectPostProcessor; import net.sourceforge.stripes.format.Formatter; import net.sourceforge.stripes.validation.TypeConverter; /** * <p> * Provides an efficient way to map "handlers" to classes. There are two types of mappings: direct * and indirect. A direct mapping is one that is explicitly created by a call to * {@link #add(Class, Object)}. An indirect mapping is one that is discovered by examining a target * type's implemented interfaces and superclasses. If {@code searchHierarchy} is set to false, then * only direct mappings will be considered and the class hierarchy will not be searched. * </p> * <p> * For example, let's assume a direct mapping is created for type {@code A} to handler {@code H}. A * request for a handler for type {@code A} returns {@code H} due to the direct mapping. If {@code * searchHierarchy} is true and a handler is requested later for type {@code B}, which implements * {@code A}, then an indirect mapping will be created that maps the handler {@code H} to type * {@code B}. (If {@code A} were a superclass of {@code B}, it would behave likewise.) However, if * {@code searchHierarchy} is false then a request for a handler for type {@code B} would return * {@link #getDefaultHandler()}. * </p> * <p> * This class is used within Stripes to map {@link Formatter}s, {@link TypeConverter}s and * {@link ObjectPostProcessor}s to specific classes and interfaces. * </p> * * @author Ben Gunter */ public class TypeHandlerCache<T> { private static final Log log = Log.getInstance(TypeHandlerCache.class); /** A direct map of target types to handlers. */ private Map<Class<?>, T> handlers = new ConcurrentHashMap<Class<?>, T>(); /** * Cache of indirect type handler results, determined by examining a target type's implemented * interfaces and superclasses. */ private Map<Class<?>, T> indirectCache = new ConcurrentHashMap<Class<?>, T>(); /** * Cache of classes that have been searched, yet no handler (besides the default one) could be * found for them. */ private Set<Class<?>> negativeCache = new ConcurrentHashSet<Class<?>>(); private T defaultHandler; private boolean searchHierarchy = true, searchAnnotations = true; /** Get the default handler to return if no handler is found for a requested target type. */ public T getDefaultHandler() { return defaultHandler; } /** Set the default handler to return if no handler is found for a requested target type. */ public void setDefaultHandler(T defaultHandler) { this.defaultHandler = defaultHandler; } /** * Indicates if the class hierarchy will be searched to find the best available handler in case * a direct mapping is not available for a given target type. Defaults to true. */ public boolean isSearchHierarchy() { return searchHierarchy; } /** * Set the flag that enables or disables searching of the class hierarchy to find the best * available handler in case a direct mapping is not available for a given target type. * * @param searchHierarchy True to enable hierarchy search; false to disable it. */ public void setSearchHierarchy(boolean searchHierarchy) { this.searchHierarchy = searchHierarchy; } /** * Indicates if the target type's annotations will be examined to find a handler registered for * the annotation class. Defaults to true. */ public boolean isSearchAnnotations() { return searchAnnotations; } /** * Set the flag that enables or disables searching for handlers for a target type's annotations. */ public void setSearchAnnotations(boolean searchAnnotations) { this.searchAnnotations = searchAnnotations; } /** * Gets the (rather confusing) map of handlers. The map uses the target type as the key in the * map, and the handler as the value. * * @return the map of classes to their handlers */ public Map<Class<?>, T> getHandlers() { return handlers; } /** * Adds a handler to the set of registered handlers, overriding an existing handler if one was * already registered for the target type. Calls {@link #clearCache()} because a new direct * mapping can affect the indirect search results. * * @param targetType The type for which a handler is requested. * @param handler The handler for the target type. */ public void add(Class<?> targetType, T handler) { handlers.put(targetType, handler); clearCache(); } /** * Check to see if the there is a handler for the specified target type. * * @param targetType The type for which a handler is requested. * @return An appropriate handler, if one is found. Otherwise, whatever is returned from a call * to {@link #getDefaultHandler()}. */ public T getHandler(Class<?> targetType) { T handler = findHandler(targetType); if (handler == null) { handler = getDefaultHandler(); log.trace("Couldn't find a handler for ", targetType, ". Using default handler ", getDefaultHandler(), " instead."); } return handler; } /** * Search for a handler class that best matches the requested class, first checking the * specified class, then all the interfaces it implements, then all its superclasses and the * interfaces they implement, and finally all the superclasses of the interfaces implemented by * {@code targetClass}. * * @param targetType The type for which a handler is requested. * @return the best applicable handler */ protected T findHandler(Class<?> targetType) { T handler = findInSuperclasses(targetType); if (handler == null) { if (isSearchHierarchy()) handler = findInInterfaces(targetType, targetType.getInterfaces()); else handler = cacheHandler(targetType, null); } return handler; } /** * Called first by {@link #findHandler(Class)}. Search for a handler class that best matches the * requested class, first checking the specified class, second all the interfaces it implements, * third annotations. If no match is found, repeat the process for each superclass. * * @param targetType The type for which a handler is requested. * @return the first applicable handler found or null if no match could be found */ protected T findInSuperclasses(Class<?> targetType) { // Check for a known handler for the class T handler; if ((handler = handlers.get(targetType)) != null) { return handler; } else if ((handler = indirectCache.get(targetType)) != null) { return handler; } else if (negativeCache.contains(targetType)) { return null; } else if (targetType.isEnum()) { handler = findInSuperclasses(Enum.class); if (handler != null) return cacheHandler(targetType, handler); } // Check directly implemented interfaces if (isSearchHierarchy()) { for (Class<?> iface : targetType.getInterfaces()) { if ((handler = handlers.get(iface)) != null) return cacheHandler(targetType, handler); else if ((handler = indirectCache.get(iface)) != null) return cacheHandler(targetType, handler); } } // Check for annotations if (isSearchAnnotations()) { for (Annotation annotation : targetType.getAnnotations()) { Class<? extends Annotation> annotationType = annotation.annotationType(); if (handlers.containsKey(annotationType)) return cacheHandler(targetType, handlers.get(annotationType)); } } // Check superclasses if (isSearchHierarchy()) { Class<?> parent = targetType.getSuperclass(); if (parent != null) { if ((handler = findInSuperclasses(parent)) != null) { return cacheHandler(targetType, handler); } } } // Nothing found, so return null return null; } /** * Called second by {@link #findHandler(Class)}, after {@link #findInSuperclasses(Class)} . * Search for a handler that best matches the requested class by checking the superclasses of * every interface implemented by {@code targetClass}. * * @param targetType The type for which a handler is requested. * @param ifaces An array of interfaces to search * @return The first applicable handler found or null if no match could be found */ protected T findInInterfaces(Class<?> targetType, Class<?>... ifaces) { T handler; for (Class<?> iface : ifaces) { if ((handler = handlers.get(iface)) != null) { return cacheHandler(targetType, handler); } else if ((handler = indirectCache.get(iface)) != null) { return cacheHandler(targetType, handler); } else if ((handler = findInInterfaces(targetType, iface.getInterfaces())) != null) { return cacheHandler(targetType, handler); } } // Nothing found, so return null return null; } /** * Cache an indirect handler mapping for the given target type. * * @param targetType The type for which a handler is requested. * @param handler The handler * @return The {@code targetType} parameter */ protected T cacheHandler(Class<?> targetType, T handler) { if (handler == null) { log.debug("Caching no handler for ", targetType); negativeCache.add(targetType); } else { log.debug("Caching handler for ", targetType, " => ", handler); indirectCache.put(targetType, handler); } return handler; } /** Clear the indirect cache. This is called by {@link #add(Class, Object)}. */ public void clearCache() { log.debug("Clearing indirect cache and negative cache"); indirectCache.clear(); negativeCache.clear(); } }