325N/A/*
325N/A * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
325N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
325N/A *
325N/A * This code is free software; you can redistribute it and/or modify it
325N/A * under the terms of the GNU General Public License version 2 only, as
325N/A * published by the Free Software Foundation. Oracle designates this
325N/A * particular file as subject to the "Classpath" exception as provided
325N/A * by Oracle in the LICENSE file that accompanied this code.
325N/A *
325N/A * This code is distributed in the hope that it will be useful, but WITHOUT
325N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
325N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
325N/A * version 2 for more details (a copy is included in the LICENSE file that
325N/A * accompanied this code).
325N/A *
325N/A * You should have received a copy of the GNU General Public License version
325N/A * 2 along with this work; if not, write to the Free Software Foundation,
325N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
325N/A *
325N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
325N/A * or visit www.oracle.com if you need additional information or have any
325N/A * questions.
325N/A */
325N/A
325N/Apackage com.sun.xml.internal.dtdparser;
325N/A
325N/Aimport java.io.InputStream;
325N/Aimport java.text.FieldPosition;
325N/Aimport java.text.MessageFormat;
325N/Aimport java.util.Hashtable;
325N/Aimport java.util.Locale;
325N/Aimport java.util.MissingResourceException;
325N/Aimport java.util.ResourceBundle;
325N/A
325N/A
325N/A/**
325N/A * This class provides support for multi-language string lookup, as needed
325N/A * to localize messages from applications supporting multiple languages
325N/A * at the same time. One class of such applications is network services,
325N/A * such as HTTP servers, which talk to clients who may not be from the
325N/A * same locale as the server. This class supports a form of negotiation
325N/A * for the language used in presenting a message from some package, where
325N/A * both user (client) preferences and application (server) support are
325N/A * accounted for when choosing locales and formatting messages.
325N/A * <p/>
325N/A * <P> Each package should have a singleton package-private message catalog
325N/A * class. This ensures that the correct class loader will always be used to
325N/A * access message resources, and minimizes use of memory: <PRE>
325N/A * package <em>some.package</em>;
325N/A * <p/>
325N/A * // "foo" might be public
325N/A * class foo {
325N/A * ...
325N/A * // package private
325N/A * static final Catalog messages = new Catalog ();
325N/A * static final class Catalog extends MessageCatalog {
325N/A * Catalog () { super (Catalog.class); }
325N/A * }
325N/A * ...
325N/A * }
325N/A * </PRE>
325N/A * <p/>
325N/A * <P> Messages for a known client could be generated using code
325N/A * something like this: <PRE>
325N/A * String clientLanguages [];
325N/A * Locale clientLocale;
325N/A * String clientMessage;
325N/A * <p/>
325N/A * // client languages will probably be provided by client,
325N/A * // e.g. by an HTTP/1.1 "Accept-Language" header.
325N/A * clientLanguages = new String [] { "en-ca", "fr-ca", "ja", "zh" };
325N/A * clientLocale = foo.messages.chooseLocale (clientLanguages);
325N/A * clientMessage = foo.messages.getMessage (clientLocale,
325N/A * "fileCount",
325N/A * new Object [] { new Integer (numberOfFiles) }
325N/A * );
325N/A * </PRE>
325N/A * <p/>
325N/A * <P> At this time, this class does not include functionality permitting
325N/A * messages to be passed around and localized after-the-fact. The consequence
325N/A * of this is that the locale for messages must be passed down through layers
325N/A * which have no normal reason to support such passdown, or else the system
325N/A * default locale must be used instead of the one the client needs.
325N/A * <p/>
325N/A * <P> <hr> The following guidelines should be used when constructiong
325N/A * multi-language applications: <OL>
325N/A * <p/>
325N/A * <LI> Always use <a href=#chooseLocale>chooseLocale</a> to select the
325N/A * locale you pass to your <code>getMessage</code> call. This lets your
325N/A * applications use IETF standard locale names, and avoids needless
325N/A * use of system defaults.
325N/A * <p/>
325N/A * <LI> The localized messages for a given package should always go in
325N/A * a separate <em>resources</em> sub-package. There are security
325N/A * implications; see below.
325N/A * <p/>
325N/A * <LI> Make sure that a language name is included in each bundle name,
325N/A * so that the developer's locale will not be inadvertently used. That
325N/A * is, don't create defaults like <em>resources/Messages.properties</em>
325N/A * or <em>resources/Messages.class</em>, since ResourceBundle will choose
325N/A * such defaults rather than giving software a chance to choose a more
325N/A * appropriate language for its messages. Your message bundles should
325N/A * have names like <em>Messages_en.properties</em> (for the "en", or
325N/A * English, language) or <em>Messages_ja.class</em> ("ja" indicates the
325N/A * Japanese language).
325N/A * <p/>
325N/A * <LI> Only use property files for messages in languages which can
325N/A * be limited to the ISO Latin/1 (8859-1) characters supported by the
325N/A * property file format. (This is mostly Western European languages.)
325N/A * Otherwise, subclass ResourceBundle to provide your messages; it is
325N/A * simplest to subclass <code>java.util.ListResourceBundle</code>.
325N/A * <p/>
325N/A * <LI> Never use another package's message catalog or resource bundles.
325N/A * It should not be possible for a change internal to one package (such
325N/A * as eliminating or improving messages) to break another package.
325N/A * <p/>
325N/A * </OL>
325N/A * <p/>
325N/A * <P> The "resources" sub-package can be treated separately from the
325N/A * package with which it is associated. That main package may be sealed
325N/A * and possibly signed, preventing other software from adding classes to
325N/A * the package which would be able to access methods and data which are
325N/A * not designed to be publicly accessible. On the other hand, resources
325N/A * such as localized messages are often provided after initial product
325N/A * shipment, without a full release cycle for the product. Such files
325N/A * (text and class files) need to be added to some package. Since they
325N/A * should not be added to the main package, the "resources" subpackage is
325N/A * used without risking the security or integrity of that main package
325N/A * as distributed in its JAR file.
325N/A *
325N/A * @author David Brownell
325N/A * @version 1.1, 00/08/05
325N/A * @see java.util.Locale
325N/A * @see java.util.ListResourceBundle
325N/A * @see java.text.MessageFormat
325N/A */
325N/A// leave this as "abstract" -- each package needs its own subclass,
325N/A// else it's not always going to be using the right class loader.
325N/Aabstract public class MessageCatalog {
325N/A private String bundleName;
325N/A
325N/A /**
325N/A * Create a message catalog for use by classes in the same package
325N/A * as the specified class. This uses <em>Messages</em> resource
325N/A * bundles in the <em>resources</em> sub-package of class passed as
325N/A * a parameter.
325N/A *
325N/A * @param packageMember Class whose package has localized messages
325N/A */
325N/A protected MessageCatalog(Class packageMember) {
325N/A this(packageMember, "Messages");
325N/A }
325N/A
325N/A /**
325N/A * Create a message catalog for use by classes in the same package
325N/A * as the specified class. This uses the specified resource
325N/A * bundle name in the <em>resources</em> sub-package of class passed
325N/A * as a parameter; for example, <em>resources.Messages</em>.
325N/A *
325N/A * @param packageMember Class whose package has localized messages
325N/A * @param bundle Name of a group of resource bundles
325N/A */
325N/A private MessageCatalog(Class packageMember, String bundle) {
325N/A int index;
325N/A
325N/A bundleName = packageMember.getName();
325N/A index = bundleName.lastIndexOf('.');
325N/A if (index == -1) // "ClassName"
325N/A bundleName = "";
325N/A else // "some.package.ClassName"
325N/A bundleName = bundleName.substring(0, index) + ".";
325N/A bundleName = bundleName + "resources." + bundle;
325N/A }
325N/A
325N/A
325N/A /**
325N/A * Get a message localized to the specified locale, using the message ID
325N/A * and package name if no message is available. The locale is normally
325N/A * that of the client of a service, chosen with knowledge that both the
325N/A * client and this server support that locale. There are two error
325N/A * cases: first, when the specified locale is unsupported or null, the
325N/A * default locale is used if possible; second, when no bundle supports
325N/A * that locale, the message ID and package name are used.
325N/A *
325N/A * @param locale The locale of the message to use. If this is null,
325N/A * the default locale will be used.
325N/A * @param messageId The ID of the message to use.
325N/A * @return The message, localized as described above.
325N/A */
325N/A public String getMessage(Locale locale,
325N/A String messageId) {
325N/A ResourceBundle bundle;
325N/A
325N/A // cope with unsupported locale...
325N/A if (locale == null)
325N/A locale = Locale.getDefault();
325N/A
325N/A try {
325N/A bundle = ResourceBundle.getBundle(bundleName, locale);
325N/A } catch (MissingResourceException e) {
325N/A bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH);
325N/A }
325N/A return bundle.getString(messageId);
325N/A }
325N/A
325N/A
325N/A /**
325N/A * Format a message localized to the specified locale, using the message
325N/A * ID with its package name if none is available. The locale is normally
325N/A * the client of a service, chosen with knowledge that both the client
325N/A * server support that locale. There are two error cases: first, if the
325N/A * specified locale is unsupported or null, the default locale is used if
325N/A * possible; second, when no bundle supports that locale, the message ID
325N/A * and package name are used.
325N/A *
325N/A * @param locale The locale of the message to use. If this is null,
325N/A * the default locale will be used.
325N/A * @param messageId The ID of the message format to use.
325N/A * @param parameters Used when formatting the message. Objects in
325N/A * this list are turned to strings if they are not Strings, Numbers,
325N/A * or Dates (that is, if MessageFormat would treat them as errors).
325N/A * @return The message, localized as described above.
325N/A * @see java.text.MessageFormat
325N/A */
325N/A public String getMessage(Locale locale,
325N/A String messageId,
325N/A Object parameters []) {
325N/A if (parameters == null)
325N/A return getMessage(locale, messageId);
325N/A
325N/A // since most messages won't be tested (sigh), be friendly to
325N/A // the inevitable developer errors of passing random data types
325N/A // to the message formatting code.
325N/A for (int i = 0; i < parameters.length; i++) {
325N/A if (!(parameters[i] instanceof String)
325N/A && !(parameters[i] instanceof Number)
325N/A && !(parameters[i] instanceof java.util.Date)) {
325N/A if (parameters[i] == null)
325N/A parameters[i] = "(null)";
325N/A else
325N/A parameters[i] = parameters[i].toString();
325N/A }
325N/A }
325N/A
325N/A // similarly, cope with unsupported locale...
325N/A if (locale == null)
325N/A locale = Locale.getDefault();
325N/A
325N/A // get the appropriately localized MessageFormat object
325N/A ResourceBundle bundle;
325N/A MessageFormat format;
325N/A
325N/A try {
325N/A bundle = ResourceBundle.getBundle(bundleName, locale);
325N/A } catch (MissingResourceException e) {
325N/A bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH);
325N/A /*String retval;
325N/A
325N/A retval = packagePrefix (messageId);
325N/A for (int i = 0; i < parameters.length; i++) {
325N/A retval += ' ';
325N/A retval += parameters [i];
325N/A }
325N/A return retval;*/
325N/A }
325N/A format = new MessageFormat(bundle.getString(messageId));
325N/A format.setLocale(locale);
325N/A
325N/A // return the formatted message
325N/A StringBuffer result = new StringBuffer();
325N/A
325N/A result = format.format(parameters, result, new FieldPosition(0));
325N/A return result.toString();
325N/A }
325N/A
325N/A
325N/A /**
325N/A * Chooses a client locale to use, using the first language specified in
325N/A * the list that is supported by this catalog. If none of the specified
325N/A * languages is supported, a null value is returned. Such a list of
325N/A * languages might be provided in an HTTP/1.1 "Accept-Language" header
325N/A * field, or through some other content negotiation mechanism.
325N/A * <p/>
325N/A * <P> The language specifiers recognized are RFC 1766 style ("fr" for
325N/A * all French, "fr-ca" for Canadian French), although only the strict
325N/A * ISO subset (two letter language and country specifiers) is currently
325N/A * supported. Java-style locale strings ("fr_CA") are also supported.
325N/A *
325N/A * @param languages Array of language specifiers, ordered with the most
325N/A * preferable one at the front. For example, "en-ca" then "fr-ca",
325N/A * followed by "zh_CN".
325N/A * @return The most preferable supported locale, or null.
325N/A * @see java.util.Locale
325N/A */
325N/A public Locale chooseLocale(String languages []) {
325N/A if ((languages = canonicalize(languages)) != null) {
325N/A for (int i = 0; i < languages.length; i++)
325N/A if (isLocaleSupported(languages[i]))
325N/A return getLocale(languages[i]);
325N/A }
325N/A return null;
325N/A }
325N/A
325N/A
325N/A //
325N/A // Canonicalizes the RFC 1766 style language strings ("en-in") to
325N/A // match standard Java usage ("en_IN"), removing strings that don't
325N/A // use two character ISO language and country codes. Avoids all
325N/A // memory allocations possible, so that if the strings passed in are
325N/A // just lowercase ISO codes (a common case) the input is returned.
325N/A //
325N/A private String[] canonicalize(String languages []) {
325N/A boolean didClone = false;
325N/A int trimCount = 0;
325N/A
325N/A if (languages == null)
325N/A return languages;
325N/A
325N/A for (int i = 0; i < languages.length; i++) {
325N/A String lang = languages[i];
325N/A int len = lang.length();
325N/A
325N/A // no RFC1766 extensions allowed; "zh" and "zh-tw" (etc) are OK
325N/A // as are regular locale names with no variant ("de_CH").
325N/A if (!(len == 2 || len == 5)) {
325N/A if (!didClone) {
325N/A languages = (String[]) languages.clone();
325N/A didClone = true;
325N/A }
325N/A languages[i] = null;
325N/A trimCount++;
325N/A continue;
325N/A }
325N/A
325N/A // language code ... if already lowercase, we change nothing
325N/A if (len == 2) {
325N/A lang = lang.toLowerCase();
325N/A if (lang != languages[i]) {
325N/A if (!didClone) {
325N/A languages = (String[]) languages.clone();
325N/A didClone = true;
325N/A }
325N/A languages[i] = lang;
325N/A }
325N/A continue;
325N/A }
325N/A
325N/A // language_country ... fixup case, force "_"
325N/A char buf [] = new char[5];
325N/A
325N/A buf[0] = Character.toLowerCase(lang.charAt(0));
325N/A buf[1] = Character.toLowerCase(lang.charAt(1));
325N/A buf[2] = '_';
325N/A buf[3] = Character.toUpperCase(lang.charAt(3));
325N/A buf[4] = Character.toUpperCase(lang.charAt(4));
325N/A if (!didClone) {
325N/A languages = (String[]) languages.clone();
325N/A didClone = true;
325N/A }
325N/A languages[i] = new String(buf);
325N/A }
325N/A
325N/A // purge any shadows of deleted RFC1766 extended language codes
325N/A if (trimCount != 0) {
325N/A String temp [] = new String[languages.length - trimCount];
325N/A int i;
325N/A
325N/A for (i = 0, trimCount = 0; i < temp.length; i++) {
325N/A while (languages[i + trimCount] == null)
325N/A trimCount++;
325N/A temp[i] = languages[i + trimCount];
325N/A }
325N/A languages = temp;
325N/A }
325N/A return languages;
325N/A }
325N/A
325N/A
325N/A //
325N/A // Returns a locale object supporting the specified locale, using
325N/A // a small cache to speed up some common languages and reduce the
325N/A // needless allocation of memory.
325N/A //
325N/A private Locale getLocale(String localeName) {
325N/A String language, country;
325N/A int index;
325N/A
325N/A index = localeName.indexOf('_');
325N/A if (index == -1) {
325N/A //
325N/A // Special case the builtin JDK languages
325N/A //
325N/A if (localeName.equals("de"))
325N/A return Locale.GERMAN;
325N/A if (localeName.equals("en"))
325N/A return Locale.ENGLISH;
325N/A if (localeName.equals("fr"))
325N/A return Locale.FRENCH;
325N/A if (localeName.equals("it"))
325N/A return Locale.ITALIAN;
325N/A if (localeName.equals("ja"))
325N/A return Locale.JAPANESE;
325N/A if (localeName.equals("ko"))
325N/A return Locale.KOREAN;
325N/A if (localeName.equals("zh"))
325N/A return Locale.CHINESE;
325N/A
325N/A language = localeName;
325N/A country = "";
325N/A } else {
325N/A if (localeName.equals("zh_CN"))
325N/A return Locale.SIMPLIFIED_CHINESE;
325N/A if (localeName.equals("zh_TW"))
325N/A return Locale.TRADITIONAL_CHINESE;
325N/A
325N/A //
325N/A // JDK also has constants for countries: en_GB, en_US, en_CA,
325N/A // fr_FR, fr_CA, de_DE, ja_JP, ko_KR. We don't use those.
325N/A //
325N/A language = localeName.substring(0, index);
325N/A country = localeName.substring(index + 1);
325N/A }
325N/A
325N/A return new Locale(language, country);
325N/A }
325N/A
325N/A
325N/A //
325N/A // cache for isLanguageSupported(), below ... key is a language
325N/A // or locale name, value is a Boolean
325N/A //
325N/A private Hashtable cache = new Hashtable(5);
325N/A
325N/A
325N/A /**
325N/A * Returns true iff the specified locale has explicit language support.
325N/A * For example, the traditional Chinese locale "zh_TW" has such support
325N/A * if there are message bundles suffixed with either "zh_TW" or "zh".
325N/A * <p/>
325N/A * <P> This method is used to bypass part of the search path mechanism
325N/A * of the <code>ResourceBundle</code> class, specifically the parts which
325N/A * force use of default locales and bundles. Such bypassing is required
325N/A * in order to enable use of a client's preferred languages. Following
325N/A * the above example, if a client prefers "zh_TW" but can also accept
325N/A * "ja", this method would be used to detect that there are no "zh_TW"
325N/A * resource bundles and hence that "ja" messages should be used. This
325N/A * bypasses the ResourceBundle mechanism which will return messages in
325N/A * some other locale (picking some hard-to-anticipate default) instead
325N/A * of reporting an error and letting the client choose another locale.
325N/A *
325N/A * @param localeName A standard Java locale name, using two character
325N/A * language codes optionally suffixed by country codes.
325N/A * @return True iff the language of that locale is supported.
325N/A * @see java.util.Locale
325N/A */
325N/A public boolean isLocaleSupported(String localeName) {
325N/A //
325N/A // Use previous results if possible. We expect that the codebase
325N/A // is immutable, so we never worry about changing the cache.
325N/A //
325N/A Boolean value = (Boolean) cache.get(localeName);
325N/A
325N/A if (value != null)
325N/A return value.booleanValue();
325N/A
325N/A //
325N/A // Try "language_country_variant", then "language_country",
325N/A // then finally "language" ... assuming the longest locale name
325N/A // is passed. If not, we'll try fewer options.
325N/A //
325N/A ClassLoader loader = null;
325N/A
325N/A for (; ;) {
325N/A String name = bundleName + "_" + localeName;
325N/A
325N/A // look up classes ...
325N/A try {
325N/A Class.forName(name);
325N/A cache.put(localeName, Boolean.TRUE);
325N/A return true;
325N/A } catch (Exception e) {
325N/A }
325N/A
325N/A // ... then property files (only for ISO Latin/1 messages)
325N/A InputStream in;
325N/A
325N/A if (loader == null)
325N/A loader = getClass().getClassLoader();
325N/A
325N/A name = name.replace('.', '/');
325N/A name = name + ".properties";
325N/A if (loader == null)
325N/A in = ClassLoader.getSystemResourceAsStream(name);
325N/A else
325N/A in = loader.getResourceAsStream(name);
325N/A if (in != null) {
325N/A cache.put(localeName, Boolean.TRUE);
325N/A return true;
325N/A }
325N/A
325N/A int index = localeName.indexOf('_');
325N/A
325N/A if (index > 0)
325N/A localeName = localeName.substring(0, index);
325N/A else
325N/A break;
325N/A }
325N/A
325N/A //
325N/A // If we got this far, we failed. Remember for later.
325N/A //
325N/A cache.put(localeName, Boolean.FALSE);
325N/A return false;
325N/A }
325N/A}