Revision: 667
          http://stripes.svn.sourceforge.net/stripes/?rev=667&view=rev
Author:   bengunter
Date:     2007-12-11 20:48:52 -0800 (Tue, 11 Dec 2007)

Log Message:
-----------
STS-456: TypeConverterFactory and FormatterFactory should handle subclasses of 
their target type. The new search algorithm for the best formatter if no exact 
match is found is to search for a match for the implemented interfaces, then 
for the superclasses, then for the superclasses of the interfaces.

Modified Paths:
--------------
    
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java

Modified: 
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java   
    2007-12-11 22:27:00 UTC (rev 666)
+++ 
trunk/stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java   
    2007-12-12 04:48:52 UTC (rev 667)
@@ -15,31 +15,43 @@
 package net.sourceforge.stripes.format;
 
 import java.util.Date;
-import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import net.sourceforge.stripes.config.Configuration;
 import net.sourceforge.stripes.tag.EncryptedValue;
 import net.sourceforge.stripes.util.Log;
 
 /**
- * Very simple default implementation of a formatter factory that is aware of 
how to format
- * dates, numbers and enums.
- *
+ * Implementation of [EMAIL PROTECTED] FormatterFactory} that contains a set 
of built-in formatters. Additional
+ * formatters can be registered by calling [EMAIL PROTECTED] #add(Class, 
Class)}. If there is no registered
+ * formatter for a specific class, then it attempts to find the best available 
formatter by
+ * searching for a match against the target implemented interfaces, class's 
superclasses, and
+ * interface superclasses.
+ * 
  * @author Tim Fennell
  */
 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 LinkedHashMap<Class<?>, Class<? extends Formatter<?>>>();
+    private Map<Class<?>, Class<? extends Formatter<?>>> formatters = new 
ConcurrentHashMap<Class<?>, Class<? extends Formatter<?>>>();
 
+    /** 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 returns. */
+    /** Stores a reference to the configuration and configures the default 
formatters. */
     public void init(Configuration configuration) throws Exception {
         this.configuration = configuration;
+
+        add(Date.class, DateFormatter.class);
+        add(Number.class, NumberFormatter.class);
+        add(Enum.class, EnumFormatter.class);
+        add(EncryptedValue.class, EncryptedValueFormatter.class);
     }
 
     /** Allows subclasses to access the stored configuration if needed. */
@@ -64,15 +76,22 @@
      * @param targetType the type for which the formatter will handle 
formatting
      * @param formatterClass the implementation class that will handle the 
formatting
      */
-    protected void add(Class<?> targetType, Class<? extends Formatter<?>> 
formatterClass) {
+    public void add(Class<?> targetType, Class<? extends Formatter<?>> 
formatterClass) {
+        if (classCache.size() > 0)
+            clearCache();
         this.formatters.put(targetType, formatterClass);
     }
 
+    /** Clear the class and instance caches. This is called by [EMAIL 
PROTECTED] #add(Class, Class)}. */
+    protected void clearCache() {
+        log.debug("Clearing formatter cache");
+        classCache.clear();
+    }
+
     /**
-     * Does a simple check to see if the clazz specified is equal to or a 
subclass of
-     * java.util.Date or java.lang.Number, and if so, creates a formatter 
instance. Otherwise
-     * returns null.
-     *
+     * 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.
+     * 
      * @param clazz the type of object being formatted
      * @param locale the Locale into which the object should be formatted
      * @param formatType the type of output to produce (e.g. date, time etc.)
@@ -80,69 +99,79 @@
      * @return Formatter an instance of a Formatter, or null
      */
     public Formatter<?> getFormatter(Class<?> clazz, Locale locale, String 
formatType, String formatPattern) {
-        // Figure out if we have a type we can format
-        Class<? extends Formatter<?>> formatterClass = null;
-        if (formatters.containsKey(clazz)) {
-            formatterClass = formatters.get(clazz);
-        }
-        else {
-            formatterClass = findFormatterClass(clazz);
-        }
-
-        // If a formatter class was found then instantiate and initialize it
+        Class<? extends Formatter<?>> formatterClass = 
findFormatterClass(clazz);
         if (formatterClass != null) {
             try {
                 return getInstance(formatterClass, formatType, formatPattern, 
locale);
             }
             catch (Exception e) {
-                Log.getInstance(getClass()).error(e, "Unable to instantiate 
Formatter ", formatterClass);
+                log.error(e, "Unable to instantiate Formatter ", 
formatterClass);
                 return null;
             }
         }
         else {
+            log.debug("Couldn't find a Formatter for ", clazz);
             return null;
         }
     }
 
     /**
-     * Searches all registered formatters looking for the first one that can 
format an object of
-     * type [EMAIL PROTECTED] targetClass}. First searches formatters added by 
calls to
-     * [EMAIL PROTECTED] #add(Class, Class)} and then searches the set of 
built-in formatters. Any formatter
-     * that can format a superclass or implemented interface of [EMAIL 
PROTECTED] targetClass} is considered a
-     * match. Thus, the order in which formatters are registered through 
[EMAIL PROTECTED] #add(Class, Class)}
-     * may be important.
+     * Search for a formatter class that best matches the requested class, 
first checking the
+     * specified class, then it's interfaces, then superclasses, and then the 
superclasses of its
+     * interfaces.
      * 
      * @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<?>> findFormatterClass(Class<?> 
targetClass) {
-        // check the formatters that have been added
-        Class<? extends Formatter<?>> formatterClass = null;
-        for (Map.Entry<Class<?>, Class<? extends Formatter<?>>> entry : 
formatters.entrySet()) {
-            if (entry.getKey().isAssignableFrom(targetClass)) {
-                formatterClass = entry.getValue();
-                break;
-            }
+        // Check for a known formatter for the class
+        if (formatters.containsKey(targetClass))
+            return formatters.get(targetClass);
+        else if (classCache.containsKey(targetClass))
+            return classCache.get(targetClass);
+
+        // Check directly implemented interfaces
+        for (Class<?> iface : targetClass.getInterfaces()) {
+            if (formatters.containsKey(iface))
+                return cacheFormatterClass(targetClass, formatters.get(iface));
+            else if (classCache.containsKey(iface))
+                return cacheFormatterClass(targetClass, classCache.get(iface));
         }
 
-        // if none found, then check the built-in formatters
-        if (formatterClass == null) {
-            if (Number.class.isAssignableFrom(targetClass)) {
-                formatterClass = NumberFormatter.class;
+        // Check superclasses
+        Class<?> parent = targetClass;
+        while ((parent = parent.getSuperclass()) != null) {
+            if (formatters.containsKey(parent))
+                return cacheFormatterClass(targetClass, 
formatters.get(parent));
+            else if (classCache.containsKey(parent))
+                return cacheFormatterClass(targetClass, 
classCache.get(parent));
+        }
+
+        // Check superclasses of implemented interfaces
+        for (Class<?> iface : targetClass.getInterfaces()) {
+            for (Class<?> superiface : iface.getInterfaces()) {
+                if (formatters.containsKey(superiface))
+                    return cacheFormatterClass(targetClass, 
formatters.get(superiface));
+                else if (classCache.containsKey(superiface))
+                    return cacheFormatterClass(targetClass, 
classCache.get(superiface));
             }
-            else if (Date.class.isAssignableFrom(targetClass)) {
-                formatterClass = DateFormatter.class;
-            }
-            else if (Enum.class.isAssignableFrom(targetClass)) {
-                formatterClass = EnumFormatter.class;
-            }
-            else if (EncryptedValue.class.isAssignableFrom(targetClass)) {
-                formatterClass = EncryptedValueFormatter.class;
-            }
         }
 
-        // cache it, even if it's null
-        formatters.put(targetClass, formatterClass);
+        // Nothing found, so cache null
+        return cacheFormatterClass(targetClass, null);
+    }
+
+    /**
+     * Add formatter class [EMAIL PROTECTED] formatterClass} for formatting 
objects of type [EMAIL PROTECTED] clazz}.
+     * 
+     * @param clazz the type of object being formatted
+     * @param formatterClass the class of the formatter
+     * @return the [EMAIL PROTECTED] 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;
     }
 


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

-------------------------------------------------------------------------
SF.Net email is sponsored by: 
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to