net.jcreate.e3.table.message.ResourceBundleMessageSource.java Source code

Java tutorial

Introduction

Here is the source code for net.jcreate.e3.table.message.ResourceBundleMessageSource.java

Source

/*
 * Copyright 2002-2005 the original author or authors.
 *
 * 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.
 */

/**
 *  E3??QQ:21523645 
 */
package net.jcreate.e3.table.message;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import net.jcreate.e3.table.util.StringUtils;

//import org.springframework.util.StringUtils;

/**
 * MessageSource that accesses the resource bundles with the specified basenames.
 * This class relies on the underlying JDK's <code>java.util.ResourceBundle</code>
 * implementation, in combination with the standard message parsing provided by
 * <code>java.text.MessageFormat</code>.
 *
 * <p>This MessageSource caches both the accessed ResourceBundle instances and
 * the generated MessageFormats for each message. It also implements rendering of
 * no-arg messages without MessageFormat, as supported by the AbstractMessageSource
 * base class. The caching provided by this MessageSource is significantly faster
 * than the built-in caching of the <code>java.util.ResourceBundle</code> class.
 *
 * <p>Unfortunately, <code>java.util.ResourceBundle</code> caches loaded bundles
 * forever: Reloading a bundle during VM execution is <i>not</i> possible.
 * As this MessageSource relies on ResourceBundle, it faces the same limitation.
 * Consider ReloadableResourceBundleMessageSource for an alternative that is
 * capable of refreshing the underlying bundle files.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see #setBasenames
 * @see ReloadableResourceBundleMessageSource
 * @see java.util.ResourceBundle
 * @see java.text.MessageFormat
 */
public class ResourceBundleMessageSource extends AbstractMessageSource {

    private String[] basenames = new String[0];

    private ClassLoader bundleClassLoader = Thread.currentThread().getContextClassLoader();

    /**
     * Cache to hold loaded ResourceBundles.
     * This Map is keyed with the bundle basename, which holds a Map that is
     * keyed with the Locale and in turn holds the ResourceBundle instances.
     * This allows for very efficient hash lookups, significantly faster
     * than the ResourceBundle class's own cache.
     */
    private final Map cachedResourceBundles = new HashMap();

    /**
     * Cache to hold already generated MessageFormats.
     * This Map is keyed with the ResourceBundle, which holds a Map that is
     * keyed with the message code, which in turn holds a Map that is keyed
     * with the Locale and holds the MessageFormat values. This allows for
     * very efficient hash lookups without concatenated keys.
     * @see #getMessageFormat
     */
    private final Map cachedBundleMessageFormats = new HashMap();

    /**
     * Set a single basename, following ResourceBundle conventions:
     * It is a fully-qualified classname. If it doesn't contain a package qualifier
     * (such as org.mypackage), it will be resolved from the classpath root.
     * <p>Messages will normally be held in the /lib or /classes directory of a WAR.
     * They can also be held in Jars on the class path. For example, a Jar in an
     * application's manifest classpath could contain messages for the application.
     * @param basename the single basename
     * @see #setBasenames
     * @see java.util.ResourceBundle
     */
    public void setBasename(String basename) {
        setBasenames(new String[] { basename });
    }

    /**
     * Set an array of basenames, each following ResourceBundle conventions.
     * The associated resource bundles will be checked sequentially when
     * resolving a message code.
     * <p>Note that message definitions in a <i>previous</i> resource bundle
     * will override ones in a later bundle, due to the sequential lookup.
     * @param basenames an array of basenames
     * @see #setBasename
     * @see java.util.ResourceBundle
     */
    public void setBasenames(String[] basenames) {
        this.basenames = (basenames != null ? basenames : new String[0]);
    }

    /**
     * Set the ClassLoader to load resource bundles with.
     * Default is the thread context ClassLoader.
     */
    public void setBundleClassLoader(ClassLoader classLoader) {
        this.bundleClassLoader = classLoader;
    }

    /**
     * Return the ClassLoader to load resource bundles with. Default is the
     * specified bundle ClassLoader, usually the thread context ClassLoader.
     */
    protected ClassLoader getBundleClassLoader() {
        return bundleClassLoader;
    }

    /**
     * Resolves the given message code as key in the registered resource bundles,
     * returning the value found in the bundle as-is (without MessageFormat parsing).
     */
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        String result = null;
        for (int i = 0; result == null && i < this.basenames.length; i++) {
            ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
            if (bundle != null) {
                result = getStringOrNull(bundle, code);
            }
        }
        return result;
    }

    /**
     * Resolves the given message code as key in the registered resource bundles,
     * using a cached MessageFormat instance per message code.
     */
    protected MessageFormat resolveCode(String code, Locale locale) {
        MessageFormat messageFormat = null;
        for (int i = 0; messageFormat == null && i < this.basenames.length; i++) {
            ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
            if (bundle != null) {
                messageFormat = getMessageFormat(bundle, code, locale);
            }
        }
        return messageFormat;
    }

    /**
     * Return a ResourceBundle for the given basename and code,
     * fetching already generated MessageFormats from the cache.
     * @param basename the basename of the ResourceBundle
     * @param locale the Locale to find the ResourceBundle for
     * @return the resulting ResourceBundle, or <code>null</code> if none
     * found for the given basename and Locale
     */
    protected ResourceBundle getResourceBundle(String basename, Locale locale) {
        synchronized (this.cachedResourceBundles) {
            Map localeMap = (Map) this.cachedResourceBundles.get(basename);
            if (localeMap != null) {
                ResourceBundle bundle = (ResourceBundle) localeMap.get(locale);
                if (bundle != null) {
                    return bundle;
                }
            }
            try {
                ResourceBundle bundle = doGetBundle(basename, locale);
                if (localeMap == null) {
                    localeMap = new HashMap();
                    this.cachedResourceBundles.put(basename, localeMap);
                }
                localeMap.put(locale, bundle);
                return bundle;
            } catch (MissingResourceException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
                }
                // Assume bundle not found
                // -> do NOT throw the exception to allow for checking parent message source.
                return null;
            }
        }
    }

    /**
     * Obtain the resource bundle for the given basename and Locale.
     * @param basename the basename to look for
     * @param locale the Locale to look for
     * @return the corresponding ResourceBundle
     * @throws MissingResourceException if no matching bundle could be found
     * @see java.util.ResourceBundle#getBundle(String, java.util.Locale, ClassLoader)
     * @see #getBundleClassLoader()
     */
    protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
        return ResourceBundle.getBundle(basename, locale, getBundleClassLoader());
    }

    /**
     * Return a MessageFormat for the given bundle and code,
     * fetching already generated MessageFormats from the cache.
     * @param bundle the ResourceBundle to work on
     * @param code the message code to retrieve
     * @param locale the Locale to use to build the MessageFormat
     * @return the resulting MessageFormat, or <code>null</code> if no message
     * defined for the given code
     */
    protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
            throws MissingResourceException {

        synchronized (this.cachedBundleMessageFormats) {
            Map codeMap = (Map) this.cachedBundleMessageFormats.get(bundle);
            Map localeMap = null;
            if (codeMap != null) {
                localeMap = (Map) codeMap.get(code);
                if (localeMap != null) {
                    MessageFormat result = (MessageFormat) localeMap.get(locale);
                    if (result != null) {
                        return result;
                    }
                }
            }

            String msg = getStringOrNull(bundle, code);
            if (msg != null) {
                if (codeMap == null) {
                    codeMap = new HashMap();
                    this.cachedBundleMessageFormats.put(bundle, codeMap);
                }
                if (localeMap == null) {
                    localeMap = new HashMap();
                    codeMap.put(code, localeMap);
                }
                MessageFormat result = createMessageFormat(msg, locale);
                localeMap.put(locale, result);
                return result;
            }

            return null;
        }
    }

    private String getStringOrNull(ResourceBundle bundle, String key) {
        try {
            return bundle.getString(key);
        } catch (MissingResourceException ex) {
            // Assume key not found
            // -> do NOT throw the exception to allow for checking parent message source.
            return null;
        }
    }

    /**
     * Show the configuration of this MessageSource.
     */
    public String toString() {
        return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames)
                + "]";
    }

}