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