You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@turbine.apache.org by ep...@apache.org on 2003/08/16 04:58:50 UTC

cvs commit: jakarta-turbine-fulcrum/localization/src/test TestComponentConfig.xml TestRoleConfig.xml

epugh       2003/08/15 19:58:50

  Added:       localization/src/java/org/apache/fulcrum/localization
                        LocaleTokenizer.java LocalizationService.java
                        DefaultLocalizationService.java
               localization/src/test/org/apache/fulcrum/localization
                        LocaleTokenizerTest.java BarBundle_en_US.java
                        FooBundle_en.java BarBundle_ko_KR.java
                        LocalizationTest.java FooBundle_fr.java
               localization/src/test TestComponentConfig.xml
                        TestRoleConfig.xml
  Log:
  adding localization sub project
  
  Revision  Changes    Path
  1.1                  jakarta-turbine-fulcrum/localization/src/java/org/apache/fulcrum/localization/LocaleTokenizer.java
  
  Index: LocaleTokenizer.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Turbine" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.Locale;
  import java.util.NoSuchElementException;
  import java.util.StringTokenizer;
  
  /**
   * Parses the HTTP <code>Accept-Language</code> header as per section
   * 14.4 of RFC 2068 (HTTP 1.1 header field definitions).
   *
   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
   * @version $Id: LocaleTokenizer.java,v 1.1 2003/08/16 02:58:50 epugh Exp $
   */
  public class LocaleTokenizer
      implements Iterator
  {
      /**
       * Separates elements of the <code>Accept-Language</code> HTTP
       * header.
       */
      private static final String LOCALE_SEPARATOR = ",";
  
      /**
       * Separates locale from quality within elements.
       */
      private static final char QUALITY_SEPARATOR = ';';
  
      /**
       * The default quality value for an <code>AcceptLanguage</code>
       * object.
       */
      private static final Float DEFAULT_QUALITY = new Float(1.0f);
  
      /**
       * The parsed locales.
       */
      private ArrayList locales = new ArrayList(3);
  
      /**
       * Parses the <code>Accept-Language</code> header.
       *
       * @param header The <code>Accept-Language</code> header
       * (i.e. <code>en, es;q=0.8, zh-TW;q=0.1</code>).
       */
      public LocaleTokenizer(String header)
      {
          StringTokenizer tok = new StringTokenizer(header, LOCALE_SEPARATOR);
          while (tok.hasMoreTokens())
          {
              AcceptLanguage acceptLang = new AcceptLanguage();
              String element = tok.nextToken().trim();
              int index;
  
              // Record and cut off any quality value that comes after a
              // semi-colon.
              if ( (index = element.indexOf(QUALITY_SEPARATOR)) != -1 )
              {
                  String q = element.substring(index);
                  element = element.substring(0, index);
                  if ( (index = q.indexOf('=')) != -1 )
                  {
                      try
                      {
                          acceptLang.quality =
                              Float.valueOf(q.substring(index + 1));
                      }
                      catch (NumberFormatException useDefault)
                      {
                      }
                  }
              }
  
              element = element.trim();
  
              // Create a Locale from the language.  A dash may separate the
              // language from the country.
              if ( (index = element.indexOf('-')) == -1 )
              {
                  // No dash means no country.
                  acceptLang.locale = new Locale(element, "");
              }
              else
              {
                  acceptLang.locale = new Locale(element.substring(0, index),
                                                 element.substring(index + 1));
              }
  
              locales.add(acceptLang);
          }
  
          // Sort by quality in descending order.
          Collections.sort(locales, Collections.reverseOrder());
      }
  
      /**
       * @return Whether there are more locales.
       */
      public boolean hasNext()
      {
          return !locales.isEmpty();
      }
  
      /**
       * Creates a <code>Locale</code> from the next element of the
       * <code>Accept-Language</code> header.
       *
       * @return The next highest-rated <code>Locale</code>.
       * @throws NoSuchElementException No more locales.
       */
      public Object next()
      {
          if (locales.isEmpty())
          {
              throw new NoSuchElementException();
          }
          return ((AcceptLanguage) locales.remove(0)).locale;
      }
  
      /**
       * Not implemented.
       */
      public final void remove()
      {
          throw new UnsupportedOperationException(getClass().getName() +
                                                  " does not support remove()");
      }
  
      /**
       * Struct representing an element of the HTTP
       * <code>Accept-Language</code> header.
       */
      private class AcceptLanguage implements Comparable
      {
          /**
           * The language and country.
           */
          Locale locale;
  
          /**
           * The quality of our locale (as values approach
           * <code>1.0</code>, they indicate increased user preference).
           */
          Float quality = DEFAULT_QUALITY;
  
          public final int compareTo(Object acceptLang)
          {
              return quality.compareTo( ((AcceptLanguage) acceptLang).quality );
          }
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/java/org/apache/fulcrum/localization/LocalizationService.java
  
  Index: LocalizationService.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Turbine" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.util.Locale;
  import java.util.ResourceBundle;
  import javax.servlet.http.HttpServletRequest;
  
  import org.apache.avalon.framework.component.Component;
  
  /**
   * <p>Provides localization functionality using the interface provided
   * by <code>ResourceBundle</code>, plus leverages a "search path"
   * style traversal of the <code>ResourceBundle</code> objects named by
   * the <code>locale.default.bundles</code> to discover a value for a
   * given key.</p>
   *
   * <p>It is suggested that one handle
   * <a href="http://www.math.fu-berlin.de/~rene/www/java/tutorial/i18n/message/messageFormat.html">dealing with concatenated messages</a>
   * using <code>MessageFormat</code> and properties files.</p>
   *
   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
   * @author <a href="mailto:leonardr@collab.net">Leonard Richardson</a>
   * @version $Id: LocalizationService.java,v 1.1 2003/08/16 02:58:50 epugh Exp $
   */
  public interface LocalizationService
      extends Component
  {
      String ROLE = LocalizationService.class.getName();
      String SERVICE_NAME = ROLE;
  
      /**
       * A constant for the HTTP <code>Accept-Language</code> header.
       */
      String ACCEPT_LANGUAGE = "Accept-Language";
  
      /**
       * Retrieves the default language (as specified in the config
       * file).
       */
      String getDefaultLanguage();
  
      /**
       * Retrieves the default country (as specified in the config
       * file).
       */
      String getDefaultCountry();
  
      /**
       * Retrieves the name of the default bundle (as specified in the
       * config file), or the first in the list if there are more than
       * one.
       */
      String getDefaultBundleName();
  
      /**
       * Retrieves the list of names of bundles to search by default for
       * <code>ResourceBundle</code> keys (as specified in the config
       * file).
       *
       * @return The list of configured bundle names.
       */
      String[] getBundleNames();
  
      /**
       * Convenience method to get the default <code>ResourceBundle</code>.
       *
       * @return A localized <code>ResourceBundle</code>.
       */
      ResourceBundle getBundle();
  
      /**
       * Returns a ResourceBundle given the bundle name and the default
       * locale information supplied by the configuration.
       *
       * @param bundleName Name of bundle.
       * @return A localized ResourceBundle.
       */
      ResourceBundle getBundle(String bundleName);
  
      /**
       * Convenience method to get a ResourceBundle based on name and
       * HTTP <code>Accept-Language</code> header.
       *
       * @param bundleName Name of bundle.
       * @param languageHeader A String with the language header.
       * @return A localized ResourceBundle.
       */
      ResourceBundle getBundle(String bundleName, String languageHeader);
  
      /**
       * Convenience method to get a ResourceBundle based on HTTP
       * Accept-Language header in HttpServletRequest.
       *
       * @param req The HTTP request to parse the
       * <code>Accept-Language</code> of.
       * @return A localized ResourceBundle.
       */
      ResourceBundle getBundle(HttpServletRequest req);
  
      /**
       * Convenience method to get a <code>ResourceBundle</code> based
       * on name and HTTP <code>Accept-Language</code> header from a
       * <code>HttpServletRequest</code>.
       *
       * @param bundleName Name of bundle.
       * @param req The HTTP request to parse the
       * <code>Accept-Language</code> of.
       * @return A localized ResourceBundle.
       */
      ResourceBundle getBundle(String bundleName, HttpServletRequest req);
  
      /**
       * Convenience method to get a ResourceBundle based on name and
       * Locale.
       *
       * @param bundleName Name of bundle.
       * @param locale A Locale.
       * @return A localized ResourceBundle.
       */
      ResourceBundle getBundle(String bundleName, Locale locale);
  
      /**
       * Attempts to pull the <code>Accept-Language</code> header out of
       * the <code>HttpServletRequest</code> object and then parse it.
       * If the header is not present, it will return a
       * <code>null</code> <code>Locale</code>.
       *
       * @param req The HTTP request to parse the
       * <code>Accept-Language</code> of.
       * @return The parsed locale.
       */
      Locale getLocale(HttpServletRequest req);
  
      /**
       * Parses the <code>Accept-Language</code> header and attempts to
       * create a <code>Locale</code> from it.
       *
       * @param header The language header (i.e. <code>en, es;q=0.8,
       * zh-TW;q=0.1</code>), or <code>null</code> for the locale
       * corresponding to the default language and country.
       * @return The parsed locale, or a locale corresponding to the
       * language and country defaults.
       */
      Locale getLocale(String languageHeader);
  
      /**
       * Tries very hard to return a value, looking first in the
       * specified bundle, then searching list of default bundles
       * (giving precedence to earlier bundles over later bundles).
       *
       * @param bundleName Name of the bundle to look in first.
       * @param locale Locale to get text for.
       * @param key Name of the text to retrieve.
       * @return Localized text.
       */
      String getString(String bundleName, Locale locale, String key);
  
      /**
       * This method sets the name of the defaultBundle.
       *
       * @param defaultBundle Name of default bundle.
       */
      void setBundle(String defaultBundle);
  
      /**
       * Formats a localized value using the provided object.
       *
       * @param bundleName The bundle in which to look for the localizable text.
       * @param locale The locale for which to format the text.
       * @param key The identifier for the localized text to retrieve,
       * @param arg1 The object to use as {0} when formatting the localized text.
       * @return Formatted localized text.
       * @see #format(String, Locale, String, Object[])
       */
      String format(String bundleName, Locale locale,
                           String key, Object arg1);
  
      /**
       * Formats a localized value using the provided objects.
       *
       * @param bundleName The bundle in which to look for the localizable text.
       * @param locale The locale for which to format the text.
       * @param key The identifier for the localized text to retrieve,
       * @param arg1 The object to use as {0} when formatting the localized text.
       * @param arg2 The object to use as {1} when formatting the localized text.
       * @return Formatted localized text.
       * @see #format(String, Locale, String, Object[])
       */
      String format(String bundleName, Locale locale,
                           String key, Object arg1, Object arg2);
  
      /**
       * Formats a localized value using the provided objects.
       *
       * @param bundleName The bundle in which to look for the localizable text.
       * @param locale The locale for which to format the text.
       * @param key The identifier for the localized text to retrieve,
       * @param args The objects to use as {0}, {1}, etc. when
       *             formatting the localized text.
       * @return Formatted localized text.
       */
      String format(String bundleName, Locale locale,
                           String key, Object[] args);
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/java/org/apache/fulcrum/localization/DefaultLocalizationService.java
  
  Index: DefaultLocalizationService.java
  ===================================================================
  package org.apache.fulcrum.localization;
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Turbine" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  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 javax.servlet.http.HttpServletRequest;
  import org.apache.commons.lang.StringUtils;
  import org.apache.log4j.Category;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.configuration.Configurable;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.logger.AbstractLogEnabled;
  import org.apache.avalon.framework.thread.ThreadSafe;
  /**
   * <p>This class is the single point of access to all localization
   * resources.  It caches different ResourceBundles for different
   * Locales.</p>
   *
   * <p>Usage example:</p>
   *
   * <blockquote><code><pre>
   * LocalizationService ls = (LocalizationService) TurbineServices
   *     .getInstance().getService(LocalizationService.SERVICE_NAME);
   * </pre></code></blockquote>
   *
   * <p>Then call {@link #getString(String, Locale, String)}, or one of
   * four methods to retrieve a ResourceBundle:
   *
   * <ul>
   * <li>getBundle("MyBundleName")</li>
   * <li>getBundle("MyBundleName", httpAcceptLanguageHeader)</li>
   * <li>etBundle("MyBundleName", HttpServletRequest)</li>
   * <li>getBundle("MyBundleName", Locale)</li>
   * <li>etc.</li>
   * </ul></p>
   *
   * @author <a href="mailto:jm@mediaphil.de">Jonas Maurus</a>
   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
   * @author <a href="mailto:novalidemail@foo.com">Frank Y. Kim</a>
   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
   * @author <a href="mailto:leonardr@collab.net">Leonard Richardson</a>
   * @version $Id: DefaultLocalizationService.java,v 1.1 2003/08/16 02:58:50 epugh Exp $
   */
  public class DefaultLocalizationService
      extends AbstractLogEnabled
      implements LocalizationService, Configurable, Initializable, ThreadSafe
  {
      /** Key Prefix for our bundles */
      private static final String BUNDLES = "bundles";
      /**
       * The value to pass to <code>MessageFormat</code> if a
       * <code>null</code> reference is passed to <code>format()</code>.
       */
      private static final Object[] NO_ARGS = new Object[0];
      /**
       * Bundle name keys a HashMap of the ResourceBundles in this
       * service (which is in turn keyed by Locale).
       */
      private HashMap bundles = null;
      /**
       * The list of default bundles to search.
       */
      private String[] bundleNames = null;
      /**
       * The default bundle name to use if not specified.
       */
      private String defaultBundleName = null;
      /**
       * The name of the default locale to use (includes language and
       * country).
       */
      private Locale defaultLocale = null;
      /** The name of the default language to use. */
      private String defaultLanguage;
      /** The name of the default country to use. */
      private String defaultCountry = null;
      /**
       * Log4J logging category.
       */
      private Category category = Category.getInstance(getClass().getName());
      /**
       * Creates a new instance.
       */
      public DefaultLocalizationService()
      {
          bundles = new HashMap();
      }
      public void configure(Configuration conf) throws ConfigurationException
      {
          Locale jvmDefault = Locale.getDefault();
          System.out.println("Using parameters");
          defaultLanguage = conf.getAttribute("locale-default-language", jvmDefault.getLanguage()).trim();
          defaultCountry = conf.getAttribute("locale-default-country", jvmDefault.getCountry()).trim();
          // FIXME! need to add bundle names
          getLogger().info("initialized lang=" + defaultLanguage + " country=" + defaultCountry);
          final Configuration bundles = conf.getChild(BUNDLES, false);
          if (bundles != null)
          {
              Configuration[] nameVal = bundles.getChildren();
              String bundleName[] = new String[nameVal.length];
              for (int i = 0; i < nameVal.length; i++)
              {
                  String key = nameVal[i].getName();
                  String val = nameVal[i].getValue();
                  getLogger().debug("Registered bundle " + val);
  				bundleName[i] = val;
              }
              initBundleNames(bundleName);
          }
      }
      /**
       * Called the first time the Service is used.
       */
      public void initialize() throws Exception
      {
         // initBundleNames(null);
          defaultLocale = new Locale(defaultLanguage, defaultCountry);
      }
      /**
       * Initialize list of default bundle names.
       *
       * @param ignored names Ignored.
       */
      protected void initBundleNames(String[] intBundleNames)
      {
          //System.err.println("cfg=" + getConfiguration());
          if (defaultBundleName != null && defaultBundleName.length() > 0)
          {
              // Using old-style single bundle name property.
              if (intBundleNames == null || intBundleNames.length <= 0)
              {
                  bundleNames = new String[] { defaultBundleName };
              }
              else
              {
                  // Prepend "default" bundle name.
                  String[] array = new String[intBundleNames.length + 1];
                  array[0] = defaultBundleName;
                  System.arraycopy(intBundleNames, 0, array, 1, intBundleNames.length);
                  bundleNames = array;
              }
          }
          if (intBundleNames == null)
          {
              bundleNames = new String[0];
          }
          bundleNames = intBundleNames;
      }
      /**
       * Retrieves the default language (specified in the config file).
       */
      public String getDefaultLanguage()
      {
          return defaultLanguage;
      }
      /**
       * Retrieves the default country (specified in the config file).
       */
      public String getDefaultCountry()
      {
          return defaultCountry;
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#getDefaultBundleName()
       */
      public String getDefaultBundleName()
      {
          return (bundleNames.length > 0 ? bundleNames[0] : "");
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#getBundleNames()
       */
      public String[] getBundleNames()
      {
          return (String[]) bundleNames.clone();
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#getBundle()
       */
      public ResourceBundle getBundle()
      {
          return getBundle(getDefaultBundleName(), (Locale) null);
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#getBundle(String)
       */
      public ResourceBundle getBundle(String bundleName)
      {
          return getBundle(bundleName, (Locale) null);
      }
      /**
       * This method returns a ResourceBundle given the bundle name and
       * the Locale information supplied in the HTTP "Accept-Language"
       * header.
       *
       * @param bundleName Name of bundle.
       * @param languageHeader A String with the language header.
       * @return A localized ResourceBundle.
       */
      public ResourceBundle getBundle(String bundleName, String languageHeader)
      {
          return getBundle(bundleName, getLocale(languageHeader));
      }
      /**
       * This method returns a ResourceBundle given the Locale
       * information supplied in the HTTP "Accept-Language" header which
       * is stored in HttpServletRequest.
       *
       * @param req HttpServletRequest.
       * @return A localized ResourceBundle.
       */
      public ResourceBundle getBundle(HttpServletRequest req)
      {
          return getBundle(getDefaultBundleName(), getLocale(req));
      }
      /**
       * This method returns a ResourceBundle given the bundle name and
       * the Locale information supplied in the HTTP "Accept-Language"
       * header which is stored in HttpServletRequest.
       *
       * @param bundleName Name of the bundle to use if the request's
       * locale cannot be resolved.
       * @param req HttpServletRequest.
       * @return A localized ResourceBundle.
       */
      public ResourceBundle getBundle(String bundleName, HttpServletRequest req)
      {
          return getBundle(bundleName, getLocale(req));
      }
      /**
       * This method returns a ResourceBundle for the given bundle name
       * and the given Locale.
       *
       * @param bundleName Name of bundle (or <code>null</code> for the
       * default bundle).
       * @param locale The locale (or <code>null</code> for the locale
       * indicated by the default language and country).
       * @return A localized ResourceBundle.
       */
      public ResourceBundle getBundle(String bundleName, Locale locale)
      {
          // Assure usable inputs.
          bundleName = (bundleName == null ? getDefaultBundleName() : bundleName.trim());
          if (locale == null)
          {
              locale = getLocale((String) null);
          }
          // Find/retrieve/cache bundle.
          ResourceBundle rb = null;
          HashMap bundlesByLocale = (HashMap) bundles.get(bundleName);
          if (bundlesByLocale != null)
          {
              // Cache of bundles by locale for the named bundle exists.
              // Check the cache for a bundle corresponding to locale.
              rb = (ResourceBundle) bundlesByLocale.get(locale);
              if (rb == null)
              {
                  // Not yet cached.
                  rb = cacheBundle(bundleName, locale);
              }
          }
          else
          {
              rb = cacheBundle(bundleName, locale);
          }
          return rb;
      }
      /**
       * Caches the named bundle for fast lookups.  This operation is
       * relatively expesive in terms of memory use, but is optimized
       * for run-time speed in the usual case.
       *
       * @exception MissingResourceException Bundle not found.
       */
      private synchronized ResourceBundle cacheBundle(String bundleName, Locale locale) throws MissingResourceException
      {
          HashMap bundlesByLocale = (HashMap) bundles.get(bundleName);
          ResourceBundle rb = (bundlesByLocale == null ? null : (ResourceBundle) bundlesByLocale.get(locale));
          if (rb == null)
          {
              bundlesByLocale = (bundlesByLocale == null ? new HashMap(3) : new HashMap(bundlesByLocale));
              try
              {
                  rb = ResourceBundle.getBundle(bundleName, locale);
              }
              catch (MissingResourceException e)
              {
                  rb = findBundleByLocale(bundleName, locale, bundlesByLocale);
                  if (rb == null)
                  {
                      throw (MissingResourceException) e.fillInStackTrace();
                  }
              }
              if (rb != null)
              {
                  // Cache bundle.
                  bundlesByLocale.put(rb.getLocale(), rb);
                  HashMap bundlesByName = new HashMap(bundles);
                  bundlesByName.put(bundleName, bundlesByLocale);
                  this.bundles = bundlesByName;
              }
          }
          return rb;
      }
      /**
       * <p>Retrieves the bundle most closely matching first against the
       * supplied inputs, then against the defaults.</p>
       *
       * <p>Use case: some clients send a HTTP Accept-Language header
       * with a value of only the language to use
       * (i.e. "Accept-Language: en"), and neglect to include a country.
       * When there is no bundle for the requested language, this method
       * can be called to try the default country (checking internally
       * to assure the requested criteria matches the default to avoid
       * disconnects between language and country).</p>
       *
       * <p>Since we're really just guessing at possible bundles to use,
       * we don't ever throw <code>MissingResourceException</code>.</p>
       */
      private ResourceBundle findBundleByLocale(String bundleName, Locale locale, Map bundlesByLocale)
      {
          ResourceBundle rb = null;
          if (!StringUtils.isNotEmpty(locale.getCountry()) && defaultLanguage.equals(locale.getLanguage()))
          {
              /*
              category.debug("Requested language '" + locale.getLanguage() +
                             "' matches default: Attempting to guess bundle " +
                             "using default country '" + defaultCountry + '\'');
              */
              Locale withDefaultCountry = new Locale(locale.getLanguage(), defaultCountry);
              rb = (ResourceBundle) bundlesByLocale.get(withDefaultCountry);
              if (rb == null)
              {
                  rb = getBundleIgnoreException(bundleName, withDefaultCountry);
              }
          }
          else if (!StringUtils.isNotEmpty(locale.getLanguage()) && defaultCountry.equals(locale.getCountry()))
          {
              Locale withDefaultLanguage = new Locale(defaultLanguage, locale.getCountry());
              rb = (ResourceBundle) bundlesByLocale.get(withDefaultLanguage);
              if (rb == null)
              {
                  rb = getBundleIgnoreException(bundleName, withDefaultLanguage);
              }
          }
          if (rb == null && !defaultLocale.equals(locale))
          {
              rb = getBundleIgnoreException(bundleName, defaultLocale);
          }
          return rb;
      }
      /**
       * Retrieves the bundle using the
       * <code>ResourceBundle.getBundle(String, Locale)</code> method,
       * returning <code>null</code> instead of throwing
       * <code>MissingResourceException</code>.
       */
      private final ResourceBundle getBundleIgnoreException(String bundleName, Locale locale)
      {
          try
          {
              return ResourceBundle.getBundle(bundleName, locale);
          }
          catch (MissingResourceException ignored)
          {
              return null;
          }
      }
      /**
       * This method sets the name of the first bundle in the search
       * list (the "default" bundle).
       *
       * @param defaultBundle Name of default bundle.
       */
      public void setBundle(String defaultBundle)
      {
          if (bundleNames.length > 0)
          {
              bundleNames[0] = defaultBundle;
          }
          else
          {
              synchronized (this)
              {
                  if (bundleNames.length <= 0)
                  {
                      bundleNames = new String[] { defaultBundle };
                  }
              }
          }
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#getLocale(HttpServletRequest)
       */
      public Locale getLocale(HttpServletRequest req)
      {
          return getLocale(req.getHeader(ACCEPT_LANGUAGE));
          // (JSS) Backed out this change because Tomcat seems to be returning
          //       the wrong result and things just are not working.
          //        Locale l = req.getLocale();
          //        return (l != null ? l : getLocale(req.getHeader(ACCEPT_LANGUAGE)));
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#getLocale(String)
       */
      public Locale getLocale(String header)
      {
          if (!StringUtils.isEmpty(header))
          {
              LocaleTokenizer tok = new LocaleTokenizer(header);
              if (tok.hasNext())
              {
                  return (Locale) tok.next();
              }
          }
          // Couldn't parse locale.
          return defaultLocale;
      }
      /**
       * @exception MissingResourceException Specified key cannot be matched.
       * @see org.apache.fulcrum.localization.LocalizationService#getString(String, Locale, String)
       */
      public String getString(String bundleName, Locale locale, String key)
      {
          String value = null;
          if (locale == null)
          {
              locale = getLocale((String) null);
          }
          // Look for text in requested bundle.
          ResourceBundle rb = getBundle(bundleName, locale);
          value = getStringOrNull(rb, key);
          // Look for text in list of default bundles.
          if (value == null && bundleNames.length > 0)
          {
              String name;
              for (int i = 0; i < bundleNames.length; i++)
              {
                  name = bundleNames[i];
                  //System.out.println("getString(): name=" + name +
                  //                   ", locale=" + locale + ", i=" + i);
                  if (!name.equals(bundleName))
                  {
                      rb = getBundle(name, locale);
                      value = getStringOrNull(rb, key);
                      if (value != null)
                      {
                          locale = rb.getLocale();
                          break;
                      }
                  }
              }
          }
          if (value == null)
          {
              String loc = locale.toString();
              String mesg =
                  LocalizationService.SERVICE_NAME
                      + " noticed missing resource: "
                      + "bundleName="
                      + bundleName
                      + ", locale="
                      + loc
                      + ", key="
                      + key;
              category.debug(mesg);
              // Text not found in requested or default bundles.
              throw new MissingResourceException(mesg, bundleName, key);
          }
          return value;
      }
      /**
       * Gets localized text from a bundle if it's there.  Otherwise,
       * returns <code>null</code> (ignoring a possible
       * <code>MissingResourceException</code>).
       */
      protected final String getStringOrNull(ResourceBundle rb, String key)
      {
          if (rb != null)
          {
              try
              {
                  return rb.getString(key);
              }
              catch (MissingResourceException ignored)
              {
              }
          }
          return null;
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#format(String, Locale, String, Object)
       */
      public String format(String bundleName, Locale locale, String key, Object arg1)
      {
          return format(bundleName, locale, key, new Object[] { arg1 });
      }
      /**
       * @see org.apache.fulcrum.localization.LocalizationService#format(String, Locale, String, Object, Object)
       */
      public String format(String bundleName, Locale locale, String key, Object arg1, Object arg2)
      {
          return format(bundleName, locale, key, new Object[] { arg1, arg2 });
      }
      /**
       * Looks up the value for <code>key</code> in the
       * <code>ResourceBundle</code> referenced by
       * <code>bundleName</code>, then formats that value for the
       * specified <code>Locale</code> using <code>args</code>.
       *
       * @return Localized, formatted text identified by
       * <code>key</code>.
       */
      public String format(String bundleName, Locale locale, String key, Object[] args)
      {
          if (locale == null)
          {
              // When formatting Date objects and such, MessageFormat
              // cannot have a null Locale.
              locale = getLocale((String) null);
          }
          String value = getString(bundleName, locale, key);
          if (args == null)
          {
              args = NO_ARGS;
          }
          // FIXME: after switching to JDK 1.4, it will be possible to clean
          // this up by providing the Locale along with the string in the
          // constructor to MessageFormat.  Until 1.4, the following workaround
          // is required for constructing the format with the appropriate locale:
          MessageFormat messageFormat = new MessageFormat("");
          messageFormat.setLocale(locale);
          messageFormat.applyPattern(value);
          return messageFormat.format(args);
      }
      /** 
       * The name used to specify this component in TurbineResources.properties 
       * @deprecated part of the pre-avalon compatibility layer
       */
      protected String getName()
      {
          return "LocalizationService";
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/org/apache/fulcrum/localization/LocaleTokenizerTest.java
  
  Index: LocaleTokenizerTest.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Turbine" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.util.Locale;
  
  import junit.framework.Test;
  import junit.framework.TestCase;
  import junit.framework.TestSuite;
  
  /**
   * Test case for the locale tokenizer.
   *
   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
   * @version $Id: LocaleTokenizerTest.java,v 1.1 2003/08/16 02:58:50 epugh Exp $
   */
  public class LocaleTokenizerTest
      extends TestCase
  {
      private static final String HEADER = "en, es;q=0.8, zh-TW;q=0.1";
  
      public LocaleTokenizerTest(String name)
      {
          super(name);
      }        
      
      public static Test suite()
      {
          return new TestSuite(LocaleTokenizerTest.class);
      }
                      
      public void testLocaleTokenizer()
      {
          try
          {
              LocaleTokenizer tok = new LocaleTokenizer(HEADER);
              Locale locale = (Locale) tok.next();
              assertEquals("Either wrong language or order parsing: " + locale,
                           locale.getLanguage(), "en");
              locale = (Locale) tok.next();
              assertEquals("Either wrong language or order parsing: " + locale,
                           locale.getLanguage(), "es");
              locale = (Locale) tok.next();
              assertEquals("Either wrong country or order parsing: " + locale,
                           locale.getCountry(), "TW");
          }
          catch (Exception e)
          {
              e.printStackTrace();
              fail();
          }
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/org/apache/fulcrum/localization/BarBundle_en_US.java
  
  Index: BarBundle_en_US.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  import java.util.ListResourceBundle;
  
  /**
   * An english resource bundle for use in testing.
   */
  public class BarBundle_en_US extends ListResourceBundle
  {
      private static final Object[][] CONTENTS =
      {
          { "key1", "value1" },
          { "key2", "value2" },
          { "key3", "value3" },
          { "key4", "value4" }
      };
      
      protected Object[][] getContents()
      {
          return CONTENTS;
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/org/apache/fulcrum/localization/FooBundle_en.java
  
  Index: FooBundle_en.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  import java.util.ListResourceBundle;
  
  /**
   * An english resource bundle for use in testing.
   */
  public class FooBundle_en extends ListResourceBundle
  {
      private static final Object[][] CONTENTS =
      {
          { "key1", "value1" },
          { "key2", "value2" },
          { "key3", "value3" },
          { "key4", "value4" }
      };
      
      protected Object[][] getContents()
      {
          return CONTENTS;
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/org/apache/fulcrum/localization/BarBundle_ko_KR.java
  
  Index: BarBundle_ko_KR.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  import java.util.ListResourceBundle;
  
  /**
   * A dummied up Korean resource bundle for use in testing.
   */
  public class BarBundle_ko_KR extends ListResourceBundle
  {
      private static final Object[][] CONTENTS =
      {
          { "key1", "[ko] value1" },
          { "key2", "[ko] value2" },
          { "key3", "[ko] value3" },
      };
      
      protected Object[][] getContents()
      {
          return CONTENTS;
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/org/apache/fulcrum/localization/LocalizationTest.java
  
  Index: LocalizationTest.java
  ===================================================================
  package org.apache.fulcrum.localization;
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   *    "Apache Turbine" must not be used to endorse or promote products
   *    derived from this software without prior written permission. For
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  import java.util.Locale;
  import java.util.MissingResourceException;
  
  import junit.framework.Test;
  import junit.framework.TestSuite;
  
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.fulcrum.testcontainer.BaseUnitTest;
  /**
   * Tests the API of the
   * {@link org.apache.fulcrum.localization.LocalizationService}.
   * <br>
   *
   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
   */
  public class LocalizationTest extends BaseUnitTest
  {
      
      private LocalizationService ls = null;
      public LocalizationTest(String name)
      {
          super(name);
      }
      public static Test suite()
      {
          return new TestSuite(LocalizationTest.class);
      }
      public void setUp()
      {
          super.setUp();
          try
          {
              ls = (LocalizationService) this.lookup(LocalizationService.ROLE);
          }
          catch (ComponentException e)
          {
              e.printStackTrace();
              fail(e.getMessage());
          }
          //        this.release(sc);
      }
      public void testInitialization()
      {
          assertTrue(true);
      }
      public void testLocalization() throws Exception
      {
          // Test retrieval of text using multiple default bundles
          String s = ls.getString(null, null, "key1");
          assertEquals("Unable to retrieve localized text for locale: default", "value1", s);
          s = ls.getString(null, new Locale("en", "US"), "key2");
          assertEquals("Unable to retrieve localized text for locale: en-US", "value2", s);
          s = ls.getString("org.apache.fulcrum.localization.BarBundle", new Locale("ko", "KR"), "key3");
          assertEquals("Unable to retrieve localized text for locale: ko-KR", s, "[ko] value3");
          try
          {
              ls.getString("DoesNotExist", new Locale("ko", ""), "key1");
              fail();
          }
          catch (MissingResourceException expectedFailure)
          {
              // Asked for resource bundle which does not exist.
          }
          // When a locale is used which cannot be produced for a given
          // bundle, fall back to the default locale.
          s = ls.getString(null, new Locale("ko", "KR"), "key4");
          assertEquals("Unable to retrieve localized text for locale: default", s, "value4");
          try
          {
              ls.getString(null, null, "NoSuchKey");
              fail();
          }
          catch (MissingResourceException expectedFailure)
          {
              // Asked for key from default bundle which does not exist,
          }
      }
      /**
       * Putting this in a seperate testcase because it fails..  Why?  I don't know.  I have never
       * used localization, so I leave it to brains better then mine. -dep
       * @throws Exception
       */
      public void testRetrievingOddLocale() throws Exception
      {
          String s = ls.getString(null, new Locale("fr", "US"), "key3");
          assertEquals("Unable to retrieve localized text for locale: fr", "[fr] value3", s);
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/org/apache/fulcrum/localization/FooBundle_fr.java
  
  Index: FooBundle_fr.java
  ===================================================================
  package org.apache.fulcrum.localization;
  
  import java.util.ListResourceBundle;
  
  /**
   * A dummied up French resource bundle for use in testing.
   */
  public class FooBundle_fr extends ListResourceBundle
  {
      private static final Object[][] CONTENTS =
      {
          { "key1", "[fr] value1" },
          { "key2", "[fr] value2" },
          { "key3", "[fr] value3" },
          { "key4", "[fr] value4" }
      };
      
      protected Object[][] getContents()
      {
          return CONTENTS;
      }
  }
  
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/TestComponentConfig.xml
  
  Index: TestComponentConfig.xml
  ===================================================================
  <componentConfig>
      <localization>
        <bundles>
          <bundle>org.apache.fulcrum.localization.BarBundle</bundle>
          <bundle>org.apache.fulcrum.localization.FooBundle</bundle>
          <bundle>org.apache.fulcrum.localization.MissingBundle</bundle>        
        </bundles>
      </localization>
  </componentConfig>
  
  
  1.1                  jakarta-turbine-fulcrum/localization/src/test/TestRoleConfig.xml
  
  Index: TestRoleConfig.xml
  ===================================================================
  <!-- This configuration file for Avalon components is used for testing the TestComponent -->
  <role-list>
      <role
          name="org.apache.fulcrum.localization.LocalizationService"
          shorthand="localization"
          default-class="org.apache.fulcrum.localization.DefaultLocalizationService"/>
  </role-list>