Revision: 1054
          http://stripes.svn.sourceforge.net/stripes/?rev=1054&view=rev
Author:   bengunter
Date:     2009-02-26 05:26:51 +0000 (Thu, 26 Feb 2009)

Log Message:
-----------
More work toward STS-614. TypeHandlerCache is now even more general-purpose and 
includes the class hierarchy scanning capabilities from 
DefaultFormatterFactory. Hierarchy scanning can now be turned on or off, as 
required by DefaultTypeConverterFactory. DefaultFormatterFactory has been 
refactored to use TypeHandlerCache with hierarchy scanning enabled.

Modified Paths:
--------------
    
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java
    trunk/stripes/src/net/sourceforge/stripes/util/TypeHandlerCache.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java

Added Paths:
-----------
    trunk/stripes/src/net/sourceforge/stripes/util/ConcurrentHashSet.java

Modified: 
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java   
    2009-02-25 22:01:10 UTC (rev 1053)
+++ 
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java   
    2009-02-26 05:26:51 UTC (rev 1054)
@@ -14,14 +14,13 @@
  */
 package net.sourceforge.stripes.format;
 
-import java.lang.annotation.Annotation;
 import java.util.Date;
 import java.util.Locale;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 
 import net.sourceforge.stripes.config.Configuration;
 import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.util.TypeHandlerCache;
 
 /**
  * Implementation of {...@link FormatterFactory} that contains a set of 
built-in formatters. Additional
@@ -35,18 +34,17 @@
 public class DefaultFormatterFactory implements FormatterFactory {
     private static final Log log = 
Log.getInstance(DefaultFormatterFactory.class);
 
-    /** A rather generic-heavy Map that maps target type to Formatter. */
-    private Map<Class<?>, Class<? extends Formatter<?>>> formatters = new 
ConcurrentHashMap<Class<?>, Class<? extends Formatter<?>>>();
+    /** Cache target type to Formatter class mappings. */
+    private TypeHandlerCache<Class<? extends Formatter<?>>> cache;
 
-    /** Cache of indirect formatter results. */
-    private Map<Class<?>, Class<? extends Formatter<?>>> classCache = new 
ConcurrentHashMap<Class<?>, Class<? extends Formatter<?>>>();
-
     /** Stores a reference to the Configuration passed in at initialization 
time. */
     private Configuration configuration;
 
     /** Stores a reference to the configuration and configures the default 
formatters. */
     public void init(Configuration configuration) throws Exception {
         this.configuration = configuration;
+        this.cache = new TypeHandlerCache<Class<? extends Formatter<?>>>();
+        this.cache.setDefaultHandler(ObjectFormatter.class);
 
         add(Date.class, DateFormatter.class);
         add(Number.class, NumberFormatter.class);
@@ -65,7 +63,7 @@
      * @return the Map of Formatter classes
      */
     protected Map<Class<?>,Class<? extends Formatter<?>>> getFormatters() {
-        return this.formatters;
+        return cache.getHandlers();
     }
 
     /**
@@ -76,16 +74,9 @@
      * @param formatterClass the implementation class that will handle the 
formatting
      */
     public void add(Class<?> targetType, Class<? extends Formatter<?>> 
formatterClass) {
-        this.formatters.put(targetType, formatterClass);
-        clearCache();
+        cache.add(targetType, formatterClass);
     }
 
-    /** Clear the class and instance caches. This is called by {...@link 
#add(Class, Class)}. */
-    protected void clearCache() {
-        log.debug("Clearing formatter cache");
-        classCache.clear();
-    }
-
     /**
      * Check to see if the there is a Formatter for the specified clazz. If a 
Formatter is found an
      * instance is created, configured and returned. Otherwise returns null.
@@ -97,7 +88,7 @@
      * @return Formatter an instance of a Formatter, or null
      */
     public Formatter<?> getFormatter(Class<?> clazz, Locale locale, String 
formatType, String formatPattern) {
-        Class<? extends Formatter<?>> formatterClass = 
findFormatterClass(clazz);
+        Class<? extends Formatter<?>> formatterClass = cache.getHandler(clazz);
         if (formatterClass != null) {
             try {
                 return getInstance(formatterClass, formatType, formatPattern, 
locale);
@@ -114,113 +105,6 @@
     }
 
     /**
-     * Search for a formatter 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 targetClass the class of the object that needs to be formatted
-     * @return the best applicable formatter
-     */
-    protected Class<? extends Formatter<?>> findFormatterClass(Class<?> 
targetClass) {
-        Class<? extends Formatter<?>> formatterClass = 
findInSuperclasses(targetClass);
-        if (formatterClass != null)
-            return formatterClass;
-
-        formatterClass = findInInterfaces(targetClass, 
targetClass.getInterfaces());
-        if (formatterClass != null)
-            return formatterClass;
-
-        return cacheFormatterClass(targetClass, ObjectFormatter.class);
-    }
-
-    /**
-     * Called first by {...@link #findFormatterClass(Class)}. Search for a 
formatter 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 targetClass the class of the object that needs to be formatted
-     * @return the first applicable formatter found or null if no match could 
be found
-     */
-    protected Class<? extends Formatter<?>> findInSuperclasses(Class<?> 
targetClass) {
-        // Check for a known formatter for the class
-        Class<? extends Formatter<?>> formatterClass;
-        if ((formatterClass = formatters.get(targetClass)) != null)
-            return formatterClass;
-        else if ((formatterClass = classCache.get(targetClass)) != null)
-            return formatterClass;
-
-        // Check directly implemented interfaces
-        for (Class<?> iface : targetClass.getInterfaces()) {
-            if ((formatterClass = formatters.get(iface)) != null)
-                return cacheFormatterClass(targetClass, formatterClass);
-            else if ((formatterClass = classCache.get(iface)) != null)
-                return cacheFormatterClass(targetClass, formatterClass);
-        }
-
-        // Check for annotations
-        for (Annotation annotation : targetClass.getAnnotations()) {
-            Class<? extends Annotation> annotationType = 
annotation.annotationType();
-            if (formatters.containsKey(annotationType))
-                return cacheFormatterClass(targetClass, 
formatters.get(annotationType));
-        }
-        
-        // Check superclasses
-        Class<?> parent = targetClass.getSuperclass();
-        if (parent != null) {
-            if ((formatterClass = findInSuperclasses(parent)) != null) {
-                return cacheFormatterClass(targetClass, formatterClass);
-            }
-        }
-
-        // Nothing found, so return null
-        return null;
-    }
-
-    /**
-     * Called second by {...@link #findFormatterClass(Class)}, after
-     * {...@link #findInSuperclasses(Class)}. Search for a formatter class 
that best matches the
-     * requested class by checking the superclasses of every interface 
implemented by
-     * {...@code targetClass}.
-     * 
-     * @param targetClass the class of the object that needs to be formatted
-     * @param ifaces an array of interfaces to search
-     * @return the first applicable formatter found or null if no match could 
be found
-     */
-    protected Class<? extends Formatter<?>> findInInterfaces(Class<?> 
targetClass,
-            Class<?>... ifaces) {
-        Class<? extends Formatter<?>> formatterClass = null;
-        for (Class<?> iface : ifaces) {
-            if ((formatterClass = formatters.get(iface)) != null) {
-                return cacheFormatterClass(targetClass, formatterClass);
-            }
-            else if ((formatterClass = classCache.get(iface)) != null) {
-                return cacheFormatterClass(targetClass, formatterClass);
-            }
-            else if ((formatterClass = findInInterfaces(targetClass, 
iface.getInterfaces())) != null) {
-                return cacheFormatterClass(targetClass, formatterClass);
-            }
-        }
-
-        // Nothing found, so return null
-        return null;
-    }
-
-    /**
-     * Add formatter class {...@code formatterClass} for formatting objects of 
type {...@code clazz}.
-     * 
-     * @param clazz the type of object being formatted
-     * @param formatterClass the class of the formatter
-     * @return the {...@code targetType} parameter
-     */
-    protected Class<? extends Formatter<?>> cacheFormatterClass(Class<?> clazz,
-            Class<? extends Formatter<?>> formatterClass) {
-        log.debug("Caching Formatter for ", clazz, " => ", formatterClass);
-        classCache.put(clazz, formatterClass);
-        return formatterClass;
-    }
-
-    /**
      * Gets an instance of the Formatter class specified.
      *
      * @param clazz the Formatter type that is desired

Added: trunk/stripes/src/net/sourceforge/stripes/util/ConcurrentHashSet.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/util/ConcurrentHashSet.java       
                        (rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/util/ConcurrentHashSet.java       
2009-02-26 05:26:51 UTC (rev 1054)
@@ -0,0 +1,142 @@
+/* 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.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A set based on {...@link ConcurrentHashMap}. The Javadoc for the 
constructors in this class were
+ * copied from the Java 1.5 Javadoc for {...@link ConcurrentHashMap} and 
changed to reflect that this
+ * is a Set and not a Map. See the Javadoc for {...@link ConcurrentHashMap} 
for information on
+ * performance characteristics, etc.
+ * 
+ * @author Ben Gunter
+ */
+public class ConcurrentHashSet<T> implements Set<T> {
+    /** The value object that will be put in the map since it does not accept 
null values. */
+    private static final Object VALUE = new Object();
+
+    /** The map that backs this set. */
+    private ConcurrentMap<T, Object> map;
+
+    /**
+     * Creates a new, empty map with a default initial capacity, load factor, 
and concurrencyLevel.
+     */
+    public ConcurrentHashSet() {
+        map = new ConcurrentHashMap<T, Object>();
+    }
+
+    /**
+     * Creates a new, empty map with the specified initial capacity, and with 
default load factor
+     * and concurrencyLevel.
+     * 
+     * @param initialCapacity the initial capacity. The implementation 
performs internal sizing to
+     *            accommodate this many elements.
+     * @throws IllegalArgumentException if the initial capacity of elements is 
negative.
+     */
+    public ConcurrentHashSet(int initialCapacity) {
+        map = new ConcurrentHashMap<T, Object>(initialCapacity);
+    }
+
+    /**
+     * Creates a new, empty map with the specified initial capacity, load 
factor, and concurrency
+     * level.
+     * 
+     * @param initialCapacity the initial capacity. The implementation 
performs internal sizing to
+     *            accommodate this many elements.
+     * @param loadFactor the load factor threshold, used to control resizing. 
Resizing may be
+     *            performed when the average number of elements per bin 
exceeds this threshold.
+     * @param concurrencyLevel - the estimated number of concurrently updating 
threads. The
+     *            implementation performs internal sizing to try to 
accommodate this many threads.
+     * @throws IllegalArgumentException if the initial capacity is negative or 
the load factor or
+     *             concurrencyLevel are nonpositive.
+     */
+    public ConcurrentHashSet(int initialCapacity, float loadFactor, int 
concurrencyLevel) {
+        map = new ConcurrentHashMap<T, Object>(initialCapacity, loadFactor, 
concurrencyLevel);
+    }
+
+    /**
+     * Creates a new set with the same elements as the given set. The set is 
created with a capacity
+     * of twice the number of elements in the given set or 11 (whichever is 
greater), and a default
+     * load factor and concurrencyLevel.
+     * 
+     * @param set The set
+     */
+    public ConcurrentHashSet(Set<? extends T> set) {
+        this(Math.max(set.size() * 2, 11));
+        addAll(set);
+    }
+
+    public boolean add(T e) {
+        return map.putIfAbsent(e, VALUE) == null;
+    }
+
+    public boolean addAll(Collection<? extends T> c) {
+        boolean b = false;
+        for (T t : c) {
+            b = b || map.putIfAbsent(t, VALUE) == null;
+        }
+        return b;
+    }
+
+    public void clear() {
+        map.clear();
+    }
+
+    public boolean contains(Object o) {
+        return map.keySet().contains(o);
+    }
+
+    public boolean containsAll(Collection<?> c) {
+        return map.keySet().containsAll(c);
+    }
+
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    public Iterator<T> iterator() {
+        return map.keySet().iterator();
+    }
+
+    public boolean remove(Object o) {
+        return map.remove(o) != null;
+    }
+
+    public boolean removeAll(Collection<?> c) {
+        return map.keySet().removeAll(c);
+    }
+
+    public boolean retainAll(Collection<?> c) {
+        return map.keySet().retainAll(c);
+    }
+
+    public int size() {
+        return map.size();
+    }
+
+    public Object[] toArray() {
+        return new ArrayList<T>(map.keySet()).toArray();
+    }
+
+    public <E> E[] toArray(E[] a) {
+        return new ArrayList<T>(map.keySet()).toArray(a);
+    }
+}

Modified: trunk/stripes/src/net/sourceforge/stripes/util/TypeHandlerCache.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/util/TypeHandlerCache.java        
2009-02-25 22:01:10 UTC (rev 1053)
+++ trunk/stripes/src/net/sourceforge/stripes/util/TypeHandlerCache.java        
2009-02-26 05:26:51 UTC (rev 1054)
@@ -16,16 +16,34 @@
 
 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;
-import net.sourceforge.stripes.validation.TypeConverterFactory;
 
 /**
- * Provides an efficient way to map "handler" classes to other classes, while 
taking into
- * consideration the target type's implemented interfaces and superclasses. 
For example,
- * {...@link TypeConverterFactory} uses this class to map an implementation of 
{...@link TypeConverter} to
- * classes.
+ * <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
  */
@@ -33,97 +51,219 @@
     private static final Log log = Log.getInstance(TypeHandlerCache.class);
 
     /** A direct map of target types to handlers. */
-    private Map<Class<?>, Class<? extends T>> handlers = new 
ConcurrentHashMap<Class<?>, Class<? extends T>>();
+    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<?>, Class<? extends T>> indirectCache = new 
ConcurrentHashMap<Class<?>, Class<? extends T>>();
+    private Map<Class<?>, T> indirectCache = new ConcurrentHashMap<Class<?>, 
T>();
 
     /**
-     * Gets the (rather confusing) Map of handler classes. The Map uses the 
target class as the key
-     * in the Map, and the Class object representing the handler as the value.
+     * 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;
+
+    /** 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.
+     */
+    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.
      * 
-     * @return the Map of classes to their handlers
+     * @param searchHierarchy True to enable hierarchy search; false to 
disable it.
      */
-    public Map<Class<?>, Class<? extends T>> getHandlers() {
+    public void setSearchHierarchy(boolean searchHierarchy) {
+        this.searchHierarchy = searchHierarchy;
+    }
+
+    /**
+     * 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 
#clearIndirectCache()} because a new
-     * direct mapping can affect the indirect search results.
+     * 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 the handler will handle conversions
-     * @param handlerClass the implementation class that will handle the 
conversions
+     * @param targetType The type for which a handler is requested.
+     * @param handler The handler for the target type.
      */
-    public void add(Class<?> targetType, Class<? extends T> handlerClass) {
-        handlers.put(targetType, handlerClass);
-        clearIndirectCache();
+    public void add(Class<?> targetType, T handler) {
+        handlers.put(targetType, handler);
+        clearCache();
     }
 
     /**
-     * Gets the applicable type handler for the class passed in.
+     * Check to see if the there is a handler for the specified target type.
      * 
-     * @param forType The target type
-     * @return The handler class associated with the target type, or null if 
none is found.
+     * @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 Class<? extends T> getHandlerClass(Class<?> forType) {
-        Class<? extends T> handlerClass = findHandlerClass(forType);
-        if (handlerClass != null) {
-            return handlerClass;
+    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.");
         }
-        else {
-            log.trace("Couldn't find a type handler for ", forType);
-            return null;
+
+        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 && isSearchHierarchy()) {
+            handler = findInInterfaces(targetType, targetType.getInterfaces());
         }
+
+        return handler;
     }
 
     /**
-     * Search for a type handler class that best matches the requested class.
+     * 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 target type
-     * @return The first applicable type handler found or null if no match 
could be found
+     * @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 Class<? extends T> findHandlerClass(Class<?> targetType) {
-        if (handlers.containsKey(targetType))
-            return handlers.get(targetType);
-        else if (indirectCache.containsKey(targetType))
-            return indirectCache.get(targetType);
+    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()) {
-            Class<? extends T> handlerClass = findHandlerClass(Enum.class);
-            if (handlerClass != null)
-                return cacheHandlerClass(targetType, handlerClass);
+            handler = findInSuperclasses(Enum.class);
+            if (handler != null)
+                return cacheHandler(targetType, handler);
         }
-        else {
-            for (Annotation annotation : targetType.getAnnotations()) {
-                Class<? extends Annotation> annotationType = 
annotation.annotationType();
-                if (handlers.containsKey(annotationType))
-                    return cacheHandlerClass(targetType, 
handlers.get(annotationType));
+        else if (!isSearchHierarchy()) {
+            return cacheHandler(targetType, null);
+        }
+
+        // Check directly implemented interfaces
+        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
+        for (Annotation annotation : targetType.getAnnotations()) {
+            Class<? extends Annotation> annotationType = 
annotation.annotationType();
+            if (handlers.containsKey(annotationType))
+                return cacheHandler(targetType, handlers.get(annotationType));
+        }
+
+        // Check superclasses
+        Class<?> parent = targetType.getSuperclass();
+        if (parent != null) {
+            if ((handler = findInSuperclasses(parent)) != null) {
+                return cacheHandler(targetType, handler);
             }
         }
 
+        // Nothing found, so return null
         return null;
     }
 
     /**
-     * Add handler class {...@code handlerClass} for converting objects of 
type {...@code clazz}.
+     * 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 clazz The type of object being converted
-     * @param handlerClass The class of the handler
+     * @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 = null;
+        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 Class<? extends T> cacheHandlerClass(Class<?> clazz, Class<? 
extends T> handlerClass) {
-        log.debug("Caching type handler for ", clazz, " => ", handlerClass);
-        indirectCache.put(clazz, handlerClass);
-        return handlerClass;
+    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, 
Class)}. */
-    public void clearIndirectCache() {
-        log.debug("Clearing indirect type handler cache");
+    public void clearCache() {
+        log.debug("Clearing indirect cache and negative cache");
         indirectCache.clear();
+        negativeCache.clear();
     }
 }

Modified: 
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java
       2009-02-25 22:01:10 UTC (rev 1053)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java
       2009-02-26 05:26:51 UTC (rev 1054)
@@ -35,7 +35,7 @@
     private static final Log log = 
Log.getInstance(DefaultTypeConverterFactory.class);
 
     /** Caches {...@link TypeConverter} to {...@link Class} mappings. */
-    private TypeHandlerCache<TypeConverter<?>> cache = new 
TypeHandlerCache<TypeConverter<?>>();
+    private TypeHandlerCache<Class<? extends TypeConverter<?>>> cache;
 
     /** Stores a reference to the Configuration passed in at initialization 
time. */
     private Configuration configuration;
@@ -45,6 +45,8 @@
      */
     public void init(final Configuration configuration) {
         this.configuration = configuration;
+        this.cache = new TypeHandlerCache<Class<? extends TypeConverter<?>>>();
+        this.cache.setSearchHierarchy(false);
 
         cache.add(Boolean.class,    BooleanTypeConverter.class);
         cache.add(Boolean.TYPE,     BooleanTypeConverter.class);
@@ -96,7 +98,6 @@
      */
     public void add(Class<?> targetType, Class<? extends TypeConverter<?>> 
converterClass) {
         cache.add(targetType, converterClass);
-        cache.clearIndirectCache();
     }
     
     /**
@@ -112,7 +113,7 @@
      */
     @SuppressWarnings("unchecked")
     public TypeConverter getTypeConverter(Class forType, Locale locale) throws 
Exception {
-        Class<? extends TypeConverter<?>> converterClass = 
cache.getHandlerClass(forType);
+        Class<? extends TypeConverter<?>> converterClass = 
cache.getHandler(forType);
         if (converterClass != null) {
             try {
                 return getInstance(converterClass, locale);


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
Open Source Business Conference (OSBC), March 24-25, 2009, San Francisco, CA
-OSBC tackles the biggest issue in open source: Open Sourcing the Enterprise
-Strategies to boost innovation and cut costs with open source participation
-Receive a $600 discount off the registration fee with the source code: SFAD
http://p.sf.net/sfu/XcvMzF8H
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to