Package ariba.util.formatter

Source Code of ariba.util.formatter.Formatter

/*
    Copyright 1996-2008 Ariba, 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.

    $Id: //ariba/platform/util/core/ariba/util/formatter/Formatter.java#18 $
*/

package ariba.util.formatter;

import ariba.util.core.Compare;
import ariba.util.core.ClassUtil;
import ariba.util.core.Assert;
import ariba.util.core.Constants;
import ariba.util.core.Date;
import ariba.util.core.MapUtil;
import java.util.Map;
import ariba.util.core.ResourceService;
import java.text.ParseException;
import java.util.Locale;
import java.text.Format;
import java.util.ArrayList;
import ariba.util.core.MultiKeyHashtable;
import ariba.util.core.Fmt;

/**
    <code>Formatter</code> and its subclasses (<code>StringFormatter</code>,
    <code>IntegerFormatter</code>, etc.) are responsible for formatting raw
    objects into strings and parsing strings back into objects.  They also
    provide methods for comparing pairs of objects for sorting purposes.
    <code>Formatter</code> instances can be used with the <code>Sort</code>
    class, since all <code>Formatters</code> implement the
    <code>Compare</code> interface.
    <p>
    The simplest way to use a formatter is in code like:
    <p>
    <blockquote>
    <code>
    String foo = Formatter.stringValue(object);
    Object bar = Formatter.parseString(string, type);
    </code>
    </blockquote>
    <p>
    Clients may also get a specific formatter for a given object or type by
    calling one of the static methods <code>formatterForObject</code> or
    <code>formatterForType</code>.
    <p>
    For improved runtime performance, this class maintains a cache of
    formatter instances, keyed by type.

    @aribaapi documented
*/
abstract public class Formatter implements StringParser, Compare
{
    private static final String UtilStringTable = "ariba.util.core";

    /*-----------------------------------------------------------------------
        Static Formatting
      -----------------------------------------------------------------------*/

    /**
        Returns a formatted string for the given object in the default locale.
        If a formatter instance for the given object can't be found, calls
        <code>toString</code> on the object and returns the resulting string.

        @param  object the object to format into a string
        @return        a string representation of the object
        @aribaapi public
    */
    public static String getStringValue (Object object)
    {
            // call the main implementation using the default locale
        return getStringValue(object, getDefaultLocale());
    }

    /**
        Returns a formatted string for the given object in the given locale.
        If a formatter instance for the given object can't be found, calls
        <code>toString</code> on the object and returns the resulting string.

        @param object the object to format into a string
        @param locale the <code>Locale</code> to use for formatting
        @return       a string representation of the object
        @aribaapi public
    */
    public static String getStringValue (Object object, Locale locale)
    {
            // can't get a formatter if the object is null
        if (object == null) {
            return "";
        }

            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

            // try to get a formatter and use it to string-ify the object
        Formatter fmt = getFormatterForObject(object);
        return (fmt == null) ? object.toString() : fmt.getFormat(object, locale);
    }


    /*-----------------------------------------------------------------------
        Static Parsing
      -----------------------------------------------------------------------*/

    /**
        Tries to parse a string to create an object of the given type using
        the default locale.  Returns the original string if a formatter
        instance for the given <code>type</code> cannot be created.

        @param  string the string to parse into an object
        @param  type   the Java type of the resulting object
        @return        an object of the given <code>type</code>
        @exception     ParseException if the string can't be parsed to create
                       an object of the given <code>type</code>
        @aribaapi public

    */
    public static Object parseString (String string, String type)
      throws ParseException
    {
        return parseString(string, type, getDefaultLocale());
    }

    /**
        Tries to parse a string to create an object of the given type using
        the given locale.  Returns the original string if a formatter instance
        for the given <code>type</code> cannot be created.

        @param  string the string to parse into an object
        @param  type   the Java type of the resulting object
        @param  locale the <code>Locale</code> to use for parsing
        @return        an object of the given <code>type</code>
        @exception     ParseException if the string can't be parsed to create
                       an object of the given <code>type</code>
        @aribaapi public
    */
    public static Object parseString (String string, String type, Locale locale)
      throws ParseException
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

        Formatter fmt = getFormatterForType(type);
        return (fmt == null) ? string : fmt.parse(string, locale);
    }


    /*-----------------------------------------------------------------------
        Static Comparison
      -----------------------------------------------------------------------*/

    /**
        Returns true if and only if the two objects should be considered equal
        in the default locale.
        <p>
        If the first object is non-null, retrieves a formatter instance based
        on its type; otherwise, uses the second object.  If a formatter can't
        be found for either object, or the types of the two objects don't
        match, resorts to the <code>equals</code> method on the first non-null
        object.  If both objects are null, returns true.

        @param  o1 the first object to test for equality
        @param  o2 the second object to test for equality
        @return    <code>true</code> if the two objects are equal;
                   <code>false</code> otherwise.
        @aribaapi public
    */
    public static boolean objectsAreEqual (Object o1, Object o2)
    {
        return objectsAreEqual(o1, o2, getDefaultLocale());
    }

    /**
        Returns true if and only if the two objects should be considered equal
        in the given locale.
        <p>
        If the first object is non-null, retrieves a formatter instance based
        on its type; otherwise, uses the second object.  If a formatter can't
        be found for either object, or the types of the two objects don't
        match, resorts to the <code>equals</code> method on the first non-null
        object.  If both objects are null, returns true.

        @param  o1     the first object to test for equality
        @param  o2     the second object to test for equality
        @param  locale the <code>Locale</code> to use for equality testing
        @return        <code>true</code> if the two objects are equal;
                       <code>false</code> otherwise.
        @aribaapi public
    */
    public static boolean objectsAreEqual (Object o1, Object o2, Locale locale)
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

            // short circuit (including both null)
        if (o1 == o2) {
            return true;
        }
            // check for one null but not the other
        else if (o1 == null || o2 == null) {
            return false;
        }

        String cls1 = (o1 == null) ? null : o1.getClass().getName();
        String cls2 = (o2 == null) ? null : o2.getClass().getName();

            // try to use a formatter for the first object
        if (o1 != null) {
            Formatter fmt = getFormatterForObject(o1);
            if (fmt != null && cls1.equals(cls2)) {
                return fmt.equal(o1, o2, locale);
            }
        }

            // try to use a formatter for the second object
        if (o2 != null) {
            Formatter fmt = getFormatterForObject(o2);
            if (fmt != null && cls2.equals(cls1)) {
                return fmt.equal(o1, o2, locale);
            }
        }

            // no formatter could be found for either object
        return (o1 != null) ? o1.equals(o2) : o2.equals(o1);
    }


    /*-----------------------------------------------------------------------
        Static Formatter Creation
      -----------------------------------------------------------------------*/

    /**
        Returns a formatter instance for the given object based on its type.

        @param  object the object for which a formatter is required
        @return        the appropriate formatter instance, or null if a
                       formatter instance can't be created for the given type
        @aribaapi documented
    */
    public static Formatter getFormatterForObject (Object object)
    {
        Assert.that(object != null, "object == null in formatterForObject()");

            // find a formatter for the type of the object
        return getFormatterForType(object.getClass().getName());
    }

    /**
        Returns a formatter instance for the given type.  Returns a cached
        instance if one exists for the given type.  Otherwise a new formatter
        instance is created and cached based on the given type.

        @param  type  the type for which a formatter is required
        @return       the appropriate formatter instance, or null if a
                      formatter instance can't be created for the given type
        @aribaapi documented
    */
    public static Formatter getFormatterForType (String type)
    {
        Assert.that(type != null, "type == null in formatterForType()");

            // check the formatter cache first
        Formatter fmt = getCachedFormatter(type);

            // if not found there, try to create one of the appropriate type
        if (fmt == null) {

                // determine the formatter class to use for this type
            String fmtClass = getFormatterClassForType(type);

            if (fmtClass != null) {
                    // create a new instance of the formatter class
                fmt = (Formatter)ClassUtil.newInstance(fmtClass, false);
                Assert.that(fmt != null, NewErrorMsg, fmtClass);

                    // cache the instance for later use
                cacheFormatter(fmt, type);
            }
        }

        return fmt;
    }

    /**
        Returns the formatter class to use for the given type.  We first check
        the class map for a perfect match with the type name.  If that fails,
        we then check the map against any interfaces implemented by the type.
        If that fails, we then call ourselves recursively with the super-type
        of the given type.  Returns null if a formatter class couldn't be
        determined for the given type, or if the type is invalid.
        @param type The fully qualified type name whose formatter we are finding
        @return The class name of the formatter, or null if there isn't any
        @aribaapi documented
    */
    public static String getFormatterClassForType (String type)
    {
        Class  typeClass = null;
        String fmtClass  = null;

            // check the class map for an exact match
        synchronized (map) {
            fmtClass = (String)map.get(type);
        }

            // check interfaces if the map check failed
        if (fmtClass == null) {
            typeClass = ClassUtil.classForName(type, false);
            if (typeClass != null) {
                Class[] interfaces = typeClass.getInterfaces();
                for (int i = 0; i < interfaces.length; i++) {
                    synchronized (map) {
                        fmtClass = (String)map.get(interfaces[i].getName());
                    }
                    if (fmtClass != null) {
                        break;
                    }
                }
            }
        }

            // check the super-type if the interfaces check failed
        if (fmtClass == null && typeClass != null) {
            Class typeSuper = typeClass.getSuperclass();
            if (typeSuper != null) {
                    // call ourselves recursively on the super-type
                return getFormatterClassForType(typeSuper.getName());
            }
        }

        return fmtClass;
    }

    /**
        Registers the given formatter class name to be associated with the
        give type.  This allows clients of the formatter class to extend the
        set of formatters that are available.  For example, the procurement
        module registers procure-specific formatter classes for types that are
        specific to that module.

        @param  type      the object type for the given formatter class
        @param  className the class of formatter to use for the given type

        @aribaapi private
    */
    public static void registerFormatter (String type, String className)
    {
        synchronized (map) {
                // store the formatter class keyed by type
            map.put(type, className);
        }

        // if we have cached a formatter for this type - remove it so that
        // we'll get the new type
        synchronized (cache) {
            cache.remove(type);
        }
    }

    /*-----------------------------------------------------------------------
        Internationalization
      -----------------------------------------------------------------------*/

    /**
        Returns the default locale to use for formatting, parsing, etc. if no
        locale is given explicitly in one of the static methods above.  The
        locale from the current resource service is used as a default.

        @return Returns the default locale
        @aribaapi documented
    */
    protected static Locale getDefaultLocale ()
    {
        return ResourceService.getService().getLocale();
    }

    /**
     * Convenience routine to get a localized message for a ParseException.
     *
     * @param errorKey - the key to the error message
     *
     * @return a newly constructed localized String
     */
    public static String makeParseExceptionMessage (String errorKey)
    {
        ResourceService rs = ResourceService.getService();
        return rs.getLocalizedString(UtilStringTable, errorKey);
    }


    /**
     * Convenience routine to get a localized message for a ParseException.
     *
     * @param errorKey - the key to the error message
     * @param argument - an argument to the error key
     *
     * @return a newly constructed localized String
     */
    public static String makeParseExceptionMessage (String errorKey, String argument)
    {
        ResourceService rs = ResourceService.getService();
        return Fmt.Sil(rs.getLocale(), UtilStringTable, errorKey, argument);
    }

    /**
     * Convenience routine to get a ParseException with a localized message.
     * @param errorKey - the key to the error message
     * @param offset - the offset in the string being parsed
     * @return a newly constructed ParseException
     */
    public static ParseException makeParseException (String errorKey,
                                                     int offset)
    {
        String msg = makeParseExceptionMessage(errorKey);
        return new ParseException(msg, offset);
    }

    /**
     * Convenience routine to get a ParseException with a localized message.
     * @param errorKey - the key to the error message
     * @param argument - an argument to the error key
     * @param offset - the offset in the string being parsed
     * @return a newly constructed ParseException
     */
    public static ParseException makeParseException (String errorKey,
                                                     String argument,
                                                     int offset)
    {
        ResourceService rs = ResourceService.getService();
        return makeParseException(errorKey, argument, offset, rs.getLocale());
    }

    public static ParseException makeParseException (String errorKey,
                                                     String argument,
                                                     int offset,
                                                     Locale locale)
    {
        String msg = Fmt.Sil(locale, UtilStringTable, errorKey, argument);
        return new ParseException(msg, offset);
    }

    /*-----------------------------------------------------------------------
        Constructor
      -----------------------------------------------------------------------*/

    /**
        Creates a new <code>Formatter</code>.  Formatters are stateless and
        thus can be cached for use on multiple values of the same type.

        @aribaapi public
    */
    public Formatter ()
    {
    }


    /*-----------------------------------------------------------------------
        Locale
      -----------------------------------------------------------------------*/

    /**
        Returns the locale associated with this formatter.  Uses the locale
        from the current resource service by default.

        @return the locale associated with this formatter
        @aribaapi public
    */
    public Locale getLocale ()
    {
        return getDefaultLocale();
    }


    /*-----------------------------------------------------------------------
        Formatter information
      -----------------------------------------------------------------------*/

    /**
        Check whether this formatter guarantees text->object conversion.<br>
        All formatters can convert from object to string, but not all can
        do the reverse conversion.  This method checks whether this formatter
        does the reverse conversion.  By default we assume that the formatter
        can do the text -> object conversion, subclasses should override if
        this is not true.
        @return true if the formatter can do text to object conversion
        @aribaapi documented
    */
    public boolean isBidirectional ()
    {
        return true;
    }

    /**
        Check whether this formatter handles null values.<br>
        Most formatters do not handle null values, and so the user of the formatter
        is expected to handle the null value themselves.  Formatters which
        handle nulls should override this method, returning true, to indicate
        that they do handle null values.
        @return true if the formatter expectes to handle null values
        @aribaapi documented
    */
    public boolean canFormatNulls ()
    {
        return false;
    }

    /*-----------------------------------------------------------------------
        Formatting
      -----------------------------------------------------------------------*/

    /**
        Returns a string representation of the given object in the default
        locale.  Returns an empty string if the object is null.

        @param object the object to format as a string
        @return       a string representation of the object
        @aribaapi documented
    */
    public final String getFormat (Object object)
    {
        return getFormat(object, getLocale());
    }

    /**
        Returns a string representation of the given object in the given
        locale.  Returns an empty string if the object is null.

        @param object the object to format as a string
        @param locale the <code>Locale</code> to use for formatting
        @return       a string representation of the object
        @aribaapi public
    */
    public final String getFormat (Object object, Locale locale)
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

            // call the internal protected implementation
        return (object != null || handlesNulls()) ? formatObject(object, locale) : "";
    }

    /**
        Check whether this class handles null values.
        @return true if this formatter class handles null values itself
        @aribaapi documented
    */
    public final boolean handlesNulls ()
    {
        /*
            Note: The reason that this method does an instanceof, rather than the
            better approach of calling a method (which some subclasses could override) is to be
            absolutely consistent with AWTextField's means of deciding which formatters handle
            nulls, which is with the marker interface.
        */
        return (this instanceof FormatterHandlesNulls);
    }

    /**
        Returns a string representation of the given object in the given
        locale.  The object is assumed to be non-null.
        <p>
        Subclasses must define this method to provide type-specific
        formatting.

        @param  object the object to format as a string
        @param  locale the <code>Locale</code> to use for formatting
        @return        a string representation of the object
        @aribaapi public
    */
    abstract protected String formatObject (Object object, Locale locale);


    /*-----------------------------------------------------------------------
        StringParser Interface
      -----------------------------------------------------------------------*/

    /**
        Tries to parse the given string into an object of the appropriate type
        for this formatter.  Uses the default locale to parse the string.

        @param  string the string to parse
        @return        an object of the appropriate type for this formatter
        @exception     ParseException if the string can't be parsed to create
                       an object the appropriate type
        @aribaapi documented
    */
    public final Object parse (String string)
      throws ParseException
    {
            // call protected parse method w/ trimmed string
        return parse(string, getLocale());
    }

    /**
        Tries to parse the given string into an object of the appropriate type
        for this formatter.  Uses the given locale to parse the string.

        @param  string the string to parse
        @param  locale the <code>Locale</code> to use for parsing
        @return        an object of the appropriate type for this formatter
        @exception     ParseException if the string can't be parsed to create
                       an object the appropriate type
        @aribaapi public
    */
    public final Object parse (String string, Locale locale)
      throws ParseException
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

            // short circuit if string is null
        if (string == null) {
            return null;
        }

            // call protected parse method w/ trimmed string
        return parseString(string.trim(), locale);
    }

    /**
        Parses the given string into an object of the appropriate type for
        this formatter.  The string is assumed to be non-null and trimmed of
        leading and trailing whitespace.
        <p>
        Subclasses must define this method to provide type-specific parsing.

        @param  string the string to parse
        @param  locale the <code>Locale</code> to use for parsing
        @return        an object of the appropriate type for this formatter
        @exception     ParseException if the string can't be parsed to create
                       an object the appropriate type
        @aribaapi public
    */
    abstract protected Object parseString (String string, Locale locale)
      throws ParseException;

    /**
        Returns an object of the appropriate type for this formatter derived
        from the given object.  The default locale is used for any conversion
        that may be done.
        <p>
        The type of the given object can by anything; it is up to each
        specific implementation to do the appropriate conversion, parsing,
        etc. to create a value of the appropriate type.
        <p>
        The return value may be null depending on the specific implementation
        for a given formatter.

        @param  object the object to convert to the type for this formatter
        @return        an object of the appropriate type for this formatter
        @aribaapi documented
    */
    public final Object getValue (Object object)
    {
        return getValue(object, getLocale());
    }

    /**
        Returns an object of the appropriate type for this formatter based on
        the given object.  The given locale is used for any conversion that
        may be done.
        <p>
        The type of the given object can by anything; it is up to each
        specific implementation to do the appropriate conversion, parsing,
        etc. to create a value of the appropriate type.
        <p>
        The return value may be null depending on the specific implementation
        for a given formatter.
        <p>
        Subclasses must define this method to provide type-specific
        conversion.

        @param  object the object to convert to the type for this formatter
        @param  locale the locale used when converting the object
        @return        an object of the appropriate type for this formatter
        @aribaapi public
    */
    abstract public Object getValue (Object object, Locale locale);


    /*-----------------------------------------------------------------------
        Equality Testing
      -----------------------------------------------------------------------*/

    /**
        Returns true if and only if the two objects should be considered equal
        in the default locale.  Returns true if the two objects are, in fact,
        the same object or both are null.  Otherwise, returns false if exactly
        one of the objects is null.  Otherwise, calls the protected method
        <code>objectsEqual</code> to determine if the two (non-null) objects
        are equal.

        @param  o1     the first object to test for equality
        @param  o2     the second object to test for equality
        @return        <code>true</code> if the two objects are equal;
                       <code>false</code> otherwise.
        @aribaapi documented
    */
    public final boolean equal (Object o1, Object o2)
    {
        return equal(o1, o2, getLocale());
    }

    /**
        Returns true if and only if the two objects should be considered equal
        in the given locale.  Returns true if the two objects are, in fact,
        the same object or both are null.  Otherwise, returns false if exactly
        one of the objects is null.  Otherwise, calls the protected method
        <code>objectsEqual</code> to determine if the two (non-null) objects
        are equal.

        @param  o1     the first object to test for equality
        @param  o2     the second object to test for equality
        @param  locale the <code>Locale</code> to use for equality testing
        @return        <code>true</code> if the two objects are equal;
                       <code>false</code> otherwise.
        @aribaapi public
    */
    public final boolean equal (Object o1, Object o2, Locale locale)
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

        if (o1 == o2) {
            return true;
        }
        else if (o1 == null || o2 == null) {
            return false;
        }
        else {
            return objectsEqual(o1, o2, locale);
        }
    }

    /**
        Returns true if and only if the two objects should be considered equal
        in the given locale.  The two values must be non-null and of the type
        appropriate for this formatter.
        <p>
        This default implementation just uses the compareObjects() method to
        test if the two objects compare equally.  Subclasses may override this
        method to provide more specific or strict equality testing.

        @param  o1     the first object to test for equality
        @param  o2     the second object to test for equality
        @param  locale the <code>Locale</code> to use for equality testing
        @return        <code>true</code> if the two objects are equal;
                       <code>false</code> otherwise.
        @aribaapi public
    */
    protected boolean objectsEqual (Object o1, Object o2, Locale locale)
    {
        return (compareObjects(o1, o2, locale) == 0);
    }


    /*-----------------------------------------------------------------------
        Compare Interface
      -----------------------------------------------------------------------*/

    /**
        Compares two objects for sorting purposes in the default locale.
        Returns an <code>int</code> value which is less than, equal to, or
        greater than zero depending on whether the first object sorts before,
        the same, or after the second object.
        <p>
        Returns zero if the two objects are, in fact, the same object or both
        are null.  Otherwise, arbitrarily returns -1 if the first object is
        null, or 1 if the second is null.  Otherwise, calls the protected
        method <code>compareObjects</code> to compare the two (non-null)
        objects.

        @param  o1     the first object to compare
        @param  o2     the second object to compare
        @return        <code>int</code> value which determines how the two
                       objects should be ordered
        @aribaapi documented
    */
    public final int compare (Object o1, Object o2)
    {
        return compare(o1, o2, getLocale());
    }

    /**
        Compares two objects for sorting purposes in the given locale.
        Returns an <code>int</code> value which is less than, equal to, or
        greater than zero depending on whether the first object sorts before,
        the same, or after the second object.
        <p>
        Returns zero if the two objects are, in fact, the same object or both
        are null.  Otherwise, arbitrarily returns -1 if the first object is
        null, or 1 if the second is null.  Otherwise, calls the protected
        method <code>compareObjects</code> to compare the two (non-null)
        objects.

        @param  o1     the first object to compare
        @param  o2     the second object to compare
        @param  locale the <code>Locale</code> to use for comparison
        @return        <code>int</code> value which determines how the two
                       objects should be ordered
        @aribaapi public
    */
    public final int compare (Object o1, Object o2, Locale locale)
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

        if (o1 == o2) {
            return 0;
        }
        else if (o1 == null) {
            return -1;
        }
        else if (o2 == null) {
            return 1;
        }
        else {
            return compareObjects(o1, o2, locale);
        }
    }

    /**
        Compares two objects for sorting purposes in the given locale.
        Returns an <code>int</code> value which is less than, equal to, or
        greater than zero depending on whether the first object sorts before,
        the same, or after the second object.
        <p>
        Subclasses must define this method to provide type-specific
        comparison.

        @param  o1     the first object to compare
        @param  o2     the second object to compare
        @param  locale the <code>Locale</code> to use for comparison
        @return        <code>int</code> value which determines how the two
                       objects should be ordered
        @aribaapi public
    */
    abstract protected int compareObjects (Object o1, Object o2, Locale locale);


    /*-----------------------------------------------------------------------
        Quick Comparison
      -----------------------------------------------------------------------*/

    /**
        Compares two objects for sorting purposes in the default locale.
        <p>
        This method is a performance optimzation for sorting.  If comparing
        two values of a given type involves some conversion or processing that
        is too expensive to do for each comparison, this method and the
        <code>quickCompareValue</code> method below can be used to precompute
        the values to use for sorting.  Any code which needs to sort these
        types should call this method rather than <code>compare</code..

        @aribaapi private
    */
    public final int quickCompare (Object o1, Object o2)
    {
        return quickCompare(o1, o2, getLocale());
    }

    /**
        Compares two objects for sorting purposes in the given locale.
        <p>
        This method is a performance optimzation for sorting.  If comparing
        two values of a given type involves some conversion or processing that
        is too expensive to do for each comparison, this method and the
        <code>quickCompareValue</code> method below can be used to precompute
        the values to use for sorting.  Any code which needs to sort these
        types should call this method rather than <code>compare</code..

        @aribaapi private
    */
    public final int quickCompare (Object o1, Object o2, Locale locale)
    {
            // locale must be non-null
        Assert.that(locale != null, "invalid null Locale");

        if (o1 == o2) {
            return 0;
        }
        else if (o1 == null) {
            return -1;
        }
        else if (o2 == null) {
            return 1;
        }
        else {
            return quickCompareObjects(o1, o2, locale);
        }
    }

    /**
        Compares two values for sorting purposes, assuming they are values
        returned by the <code>quickCompareValue</code> method below.  The
        default locale is used for comparisons.
        <p>
        This default implementation is to just call the <code>compare</code>
        method.  Any formatter which overrides <code>quickCompareValue</code>
        should also override this method to do a quick comparison of the
        converted values.

        @aribaapi private
    */
    protected int quickCompareObjects (Object o1, Object o2)
    {
        return quickCompareObjects(o1, o2, getLocale());
    }

    /**
        Compares two values for sorting purposes, assuming they are values
        returned by the <code>quickCompareValue</code> method below.  The
        default locale is used for comparisons.
        <p>
        This default implementation is to just call the <code>compare</code>
        method.  Any formatter which overrides <code>quickCompareValue</code>
        should also override this method to do a quick comparison of the
        converted values.

        @aribaapi private
    */
    protected int quickCompareObjects (Object o1, Object o2, Locale locale)
    {
        return compare(o1, o2, locale);
    }

    /**
        Returns a converted value for the given object to be used by the
        <code>quickCompare</code> method above.  The previous object is the
        previous value that was passed to <code>quickCompareValue</code>
        (pre-converted).
        <p>
        The default implementation is to just return object itself.

        @aribaapi ariba
    */
    public Object quickCompareValue (Object object, Object previous)
    {
        return object;
    }


    /*-----------------------------------------------------------------------
        Formatter Cache
      -----------------------------------------------------------------------*/

    /**
        Retrieves a formatter for the given type from our cache.

        @param  type the type of <code>Formatter</code> to retrieve
        @return      a cached <code>Formatter</code> instance, or null if an
                     instance of the given <code>type</code> wasn't found in
                     the cache
        @aribaapi documented
    */
    protected static Formatter getCachedFormatter (String type)
    {
        synchronized (cache) {
            return (Formatter)cache.get(type);
        }
    }

    /**
        Stores formatter in our cache using the given key.

        @param formatter the <code>Formatter</code> instance to cache
        @param type      the type of the <code>Formatter</code>, used as the
                         key in the cache for later retrieval
        @aribaapi documented
    */
    protected static void cacheFormatter (Formatter formatter, String type)
    {
        // only stateless formatters can go in the cache
        if (!(formatter instanceof StatefulFormatter)) {
            synchronized (cache) {
                cache.put(type, formatter);
            }
        }
    }

    /*-----------------------------------------------------------------------
        Private Constants
      -----------------------------------------------------------------------*/

        // message strings for assertions
    private static final String NewErrorMsg =
        "couldn't create formatter of type '%s'.";


    /*-----------------------------------------------------------------------
        Private Statics
      -----------------------------------------------------------------------*/

        // the object type -> formatter type map
    private static final Map<String,String> map = MapUtil.map();

        // a cache of formatter instances, keyed by type (e.g. class)
        // only stateless formatters (not instances of StatefulFormatter) are in here
    private static final Map cache = MapUtil.map();


    /*-----------------------------------------------------------------------
        Static Initialization
      -----------------------------------------------------------------------*/

    static {
        map.put(Constants.StringType,           StringFormatter.ClassName);
        map.put(Constants.BooleanType,          BooleanFormatter.ClassName);
        map.put(Constants.IntegerType,          IntegerFormatter.ClassName);
        map.put(Constants.IntPrimitiveType,     IntegerFormatter.ClassName);
        map.put(Constants.LongType,             LongFormatter.ClassName);
        map.put(Constants.LongPrimitiveType,    LongFormatter.ClassName);
        map.put(Constants.DoubleType,           DoubleFormatter.ClassName);
        map.put(Constants.DoublePrimitiveType,  DoubleFormatter.ClassName);
        map.put(Constants.BigDecimalType,       BigDecimalFormatter.ClassName);
        map.put(Date.ClassName,                 DateFormatter.ClassName);
        map.put(Constants.IntegerArrayType,     IntegerArrayFormatter.ClassName);
        map.put(Constants.IntArrayType,         IntegerArrayFormatter.ClassName);
    }


    protected static final int StringFormatterType        = 0;
    protected static final int BooleanFormatterType       = 1;
    protected static final int IntegerFormatterType       = 2;
    protected static final int LongFormatterType          = 3;
    protected static final int DoubleFormatterType        = 4;
    protected static final int BigDecimalFormatterType    = 5;
    protected static final int CalendarDateFormatterType  = 6;
    protected static final int DateFormatterType          = 7;
    protected static final int IntegerArrayTFormatterType = 8;
    protected static final int IntArrayFormatterType      = 9;

    private static MultiKeyHashtable[] formatCache = new MultiKeyHashtable[]
        {
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
            new MultiKeyHashtable(2),
        };

    protected Format instantiateFormat (int type, Locale locale, String pattern)
    {
        Assert.that(false,
            "this method should be overridden by any formatters wanting caching");
        return null;
    }

    protected Format acquireFormat (int type, Locale locale, String pattern)
    {
        MultiKeyHashtable cache = formatCache[type];
        if (pattern == null) {
            pattern = "";
        }

        synchronized(cache)
        {
            ArrayList elements = (ArrayList)cache.get(locale,pattern);
            if (elements == null) {
                elements = new ArrayList();
                cache.put(locale,pattern,elements);
            }
            if (elements.isEmpty()) {
                return instantiateFormat(type,locale,pattern);
            }
            else {
                int i = elements.size()-1;
                return (Format)elements.remove(i);
            }
        }
    }

    protected void releaseFormat (Format format,int type, Locale locale, String pattern)
    {
        if (format == null) {
            return;
        }

        if (pattern == null) {
            pattern = "";
        }

        MultiKeyHashtable cache = formatCache[type];

        synchronized(cache)
        {
            // ToDo: should add a high water limit so that formatters
            // are not cached after the cache reaches a certain size.

            ArrayList elements = (ArrayList)cache.get(locale,pattern);
            Assert.that(elements != null, "no elements list. release without acquire?");
            elements.add(format);
        }
    }
}
TOP

Related Classes of ariba.util.formatter.Formatter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.