All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oreilly.servlet.LocaleNegotiator Maven / Gradle / Ivy

The newest version!
// Copyright (C) 1998-2001 by Jason Hunter .
// All rights reserved.  Use of this class is limited.
// Please see the LICENSE for more information.

package com.oreilly.servlet;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

/** 
 * A class to aid in servlet internationalization.  It determines, from a 
 * client request, the best charset, locale, and resource bundle to use 
 * with the response.
 * <p>
 * LocaleNegotiator works by scanning through the client's language 
 * preferences (sent by browsers in the <tt>Accept-Language</tt> header)
 * looking for any 
 * language for which there exists is a corresponding resource bundle.
 * When it finds a correspondence, it uses the LocaleToCharsetMap class
 * to determine the charset.  If there's any problem, it tries to fall 
 * back to US English.  The logic currently ignores the client's charset 
 * preferences (sent in the <tt>Accept-Charset</tt> header).
 * <p>
 * It can be used like this:
 * <blockquote><pre>
 * String bundleName = "BundleName";
 * String acceptLanguage = req.getHeader("Accept-Language");
 * String acceptCharset = req.getHeader("Accept-Charset");
 *  
 * LocaleNegotiator negotiator =
 *   new LocaleNegotiator(bundleName, acceptLanguage, acceptCharset);
 *  
 * Locale locale = negotiator.getLocale();
 * String charset = negotiator.getCharset();
 * ResourceBundle bundle = negotiator.getBundle();  // may be null
 *  
 * res.setContentType("text/plain; charset=" + charset);
 * res.setHeader("Content-Language", locale.getLanguage());
 * res.setHeader("Vary", "Accept-Language");
 *  
 * PrintWriter out = res.getWriter();
 *  
 * out.println(bundle.getString("resource"));
 * </pre></blockquote>
 *
 * @see com.oreilly.servlet.LocaleToCharsetMap
 *
 * @author <b>Jason Hunter</b>, Copyright © 1998
 * @version 1.0, 98/09/18
 */
public class LocaleNegotiator {

  private ResourceBundle chosenBundle; 
  private Locale chosenLocale; 
  private String chosenCharset; 

  /**
   * Constructs a new LocaleNegotiator for the given bundle name, language
   * list, and charset list.
   *
   * @param bundleName the resource bundle name
   * @param languages the Accept-Language header
   * @param charsets the Accept-Charset header
   */
  public LocaleNegotiator(String bundleName,
                          String languages,
                          String charsets) {

    // Specify default values:
    //   English language, ISO-8859-1 (Latin-1) charset, English bundle
    Locale defaultLocale = new Locale("en", "US");
    String defaultCharset = "ISO-8859-1";
    ResourceBundle defaultBundle = null;
    try {
      defaultBundle = ResourceBundle.getBundle(bundleName, defaultLocale);
    }
    catch (MissingResourceException e) {
      // No default bundle was found.  Flying without a net.
    }

    // If the client didn't specify acceptable languages, we can keep
    // the defaults.
    if (languages == null) {
      chosenLocale = defaultLocale;
      chosenCharset = defaultCharset;
      chosenBundle = defaultBundle;
      return;  // quick exit
    }

    // Use a tokenizer to separate acceptable languages
    StringTokenizer tokenizer = new StringTokenizer(languages, ",");

    while (tokenizer.hasMoreTokens()) {
      // Get the next acceptable language.
      // (The language can look something like "en; qvalue=0.91")
      String lang = tokenizer.nextToken();

      // Get the locale for that language
      Locale loc = getLocaleForLanguage(lang);

      // Get the bundle for this locale.  Don't let the search fallback 
      // to match other languages!
      ResourceBundle bundle = getBundleNoFallback(bundleName, loc);

      // The returned bundle is null if there's no match.  In that case
      // we can't use this language since the servlet can't speak it.
      if (bundle == null) continue;  // on to the next language

      // Find a charset we can use to display that locale's language.
      String charset = getCharsetForLocale(loc, charsets);

      // The returned charset is null if there's no match.  In that case
      // we can't use this language since the servlet can't encode it.
      if (charset == null) continue;  // on to the next language

      // If we get here, there are no problems with this language.
      chosenLocale = loc;
      chosenBundle = bundle;
      chosenCharset = charset;
      return;  // we're done
    }

    // No matches, so we let the defaults stand
    chosenLocale = defaultLocale;
    chosenCharset = defaultCharset;
    chosenBundle = defaultBundle;
  }

  /**
   * Gets the chosen bundle.
   *
   * @return the chosen bundle
   */
  public ResourceBundle getBundle() {
    return chosenBundle;
  }

  /**
   * Gets the chosen locale.
   *
   * @return the chosen locale
   */
  public Locale getLocale() {
    return chosenLocale;
  }

  /**
   * Gets the chosen charset.
   *
   * @return the chosen charset
   */
  public String getCharset() {
    return chosenCharset;
  }

  /*
   * Gets a Locale object for a given language string
   */
  private Locale getLocaleForLanguage(String lang) {
    Locale loc;
    int semi, dash;

    // Cut off any qvalue that might come after a semi-colon
    if ((semi = lang.indexOf(';')) != -1) {
      lang = lang.substring(0, semi);
    }

    // Trim any whitespace
    lang = lang.trim();

    // Create a Locale from the language.  A dash may separate the
    // language from the country.
    if ((dash = lang.indexOf('-')) == -1) {
      loc = new Locale(lang, "");  // No dash, no country
    }
    else {
      loc = new Locale(lang.substring(0, dash), lang.substring(dash+1));
    }

    return loc;
  }

  /*
   * Gets a ResourceBundle object for the given bundle name and locale,
   * or null if the bundle can't be found.  The resource bundle must match
   * the locale exactly.  Fallback matches are not permitted.
   */
  private ResourceBundle getBundleNoFallback(String bundleName, Locale loc) {

    // First get the fallback bundle -- the bundle that will be selected 
    // if getBundle() can't find a direct match.  This bundle can be
    // compared to the bundles returned by later calls to getBundle() in
    // order to detect when getBundle() finds a direct match.
    ResourceBundle fallback = null;
    try {
      fallback =
        ResourceBundle.getBundle(bundleName, new Locale("bogus", ""));
    }
    catch (MissingResourceException e) {
      // No fallback bundle was found.
    }

    try {
      // Get the bundle for the specified locale
      ResourceBundle bundle = ResourceBundle.getBundle(bundleName, loc);

      // Is the bundle different than our fallback bundle?
      if (bundle != fallback) {
        // We have a real match!
        return bundle;
      }
      // So the bundle is the same as our fallback bundle.
      // We can still have a match, but only if our locale's language 
      // matches the default locale's language.
      else if (bundle == fallback &&
            loc.getLanguage().equals(Locale.getDefault().getLanguage())) {
        // Another way to match
        return bundle;
      }
      else {
        // No match, keep looking
      }
    }
    catch (MissingResourceException e) {
      // No bundle available for this locale
    }

    return null;  // no match
  }

  /**
   * Gets the best charset for a given locale, selecting from a charset list.
   * Currently ignores the charset list.  Subclasses can override this 
   * method to take the list into account.
   *
   * @param loc the locale
   * @param charsets a comma-separated charset list
   * @return the best charset for the given locale from the given list
   */
  protected String getCharsetForLocale(Locale loc, String charsets) {
    // Note: This method ignores the client-specified charsets
    return LocaleToCharsetMap.getCharset(loc);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy