src/share/jaxws_classes/com/sun/xml/internal/dtdparser/MessageCatalog.java

Thu, 31 Aug 2017 15:18:52 +0800

author
aoqi
date
Thu, 31 Aug 2017 15:18:52 +0800
changeset 637
9c07ef4934dd
parent 397
b99d7e355d4b
parent 0
373ffda63c9a
permissions
-rw-r--r--

merge

     1 /*
     2  * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    26 package com.sun.xml.internal.dtdparser;
    28 import java.io.InputStream;
    29 import java.text.FieldPosition;
    30 import java.text.MessageFormat;
    31 import java.util.Hashtable;
    32 import java.util.Locale;
    33 import java.util.MissingResourceException;
    34 import java.util.ResourceBundle;
    37 /**
    38  * This class provides support for multi-language string lookup, as needed
    39  * to localize messages from applications supporting multiple languages
    40  * at the same time.  One class of such applications is network services,
    41  * such as HTTP servers, which talk to clients who may not be from the
    42  * same locale as the server.  This class supports a form of negotiation
    43  * for the language used in presenting a message from some package, where
    44  * both user (client) preferences and application (server) support are
    45  * accounted for when choosing locales and formatting messages.
    46  * <p/>
    47  * <P> Each package should have a singleton package-private message catalog
    48  * class.  This ensures that the correct class loader will always be used to
    49  * access message resources, and minimizes use of memory: <PRE>
    50  * package <em>some.package</em>;
    51  * <p/>
    52  * // "foo" might be public
    53  * class foo {
    54  * ...
    55  * // package private
    56  * static final Catalog messages = new Catalog ();
    57  * static final class Catalog extends MessageCatalog {
    58  * Catalog () { super (Catalog.class); }
    59  * }
    60  * ...
    61  * }
    62  * </PRE>
    63  * <p/>
    64  * <P> Messages for a known client could be generated using code
    65  * something like this:  <PRE>
    66  * String clientLanguages [];
    67  * Locale clientLocale;
    68  * String clientMessage;
    69  * <p/>
    70  * // client languages will probably be provided by client,
    71  * // e.g. by an HTTP/1.1 "Accept-Language" header.
    72  * clientLanguages = new String [] { "en-ca", "fr-ca", "ja", "zh" };
    73  * clientLocale = foo.messages.chooseLocale (clientLanguages);
    74  * clientMessage = foo.messages.getMessage (clientLocale,
    75  * "fileCount",
    76  * new Object [] { new Integer (numberOfFiles) }
    77  * );
    78  * </PRE>
    79  * <p/>
    80  * <P> At this time, this class does not include functionality permitting
    81  * messages to be passed around and localized after-the-fact.  The consequence
    82  * of this is that the locale for messages must be passed down through layers
    83  * which have no normal reason to support such passdown, or else the system
    84  * default locale must be used instead of the one the client needs.
    85  * <p/>
    86  * <P> <hr> The following guidelines should be used when constructiong
    87  * multi-language applications:  <OL>
    88  * <p/>
    89  * <LI> Always use <a href=#chooseLocale>chooseLocale</a> to select the
    90  * locale you pass to your <code>getMessage</code> call.  This lets your
    91  * applications use IETF standard locale names, and avoids needless
    92  * use of system defaults.
    93  * <p/>
    94  * <LI> The localized messages for a given package should always go in
    95  * a separate <em>resources</em> sub-package.  There are security
    96  * implications; see below.
    97  * <p/>
    98  * <LI> Make sure that a language name is included in each bundle name,
    99  * so that the developer's locale will not be inadvertently used. That
   100  * is, don't create defaults like <em>resources/Messages.properties</em>
   101  * or <em>resources/Messages.class</em>, since ResourceBundle will choose
   102  * such defaults rather than giving software a chance to choose a more
   103  * appropriate language for its messages.  Your message bundles should
   104  * have names like <em>Messages_en.properties</em> (for the "en", or
   105  * English, language) or <em>Messages_ja.class</em> ("ja" indicates the
   106  * Japanese language).
   107  * <p/>
   108  * <LI> Only use property files for messages in languages which can
   109  * be limited to the ISO Latin/1 (8859-1) characters supported by the
   110  * property file format.  (This is mostly Western European languages.)
   111  * Otherwise, subclass ResourceBundle to provide your messages; it is
   112  * simplest to subclass <code>java.util.ListResourceBundle</code>.
   113  * <p/>
   114  * <LI> Never use another package's message catalog or resource bundles.
   115  * It should not be possible for a change internal to one package (such
   116  * as eliminating or improving messages) to break another package.
   117  * <p/>
   118  * </OL>
   119  * <p/>
   120  * <P> The "resources" sub-package can be treated separately from the
   121  * package with which it is associated.  That main package may be sealed
   122  * and possibly signed, preventing other software from adding classes to
   123  * the package which would be able to access methods and data which are
   124  * not designed to be publicly accessible.  On the other hand, resources
   125  * such as localized messages are often provided after initial product
   126  * shipment, without a full release cycle for the product.  Such files
   127  * (text and class files) need to be added to some package.  Since they
   128  * should not be added to the main package, the "resources" subpackage is
   129  * used without risking the security or integrity of that main package
   130  * as distributed in its JAR file.
   131  *
   132  * @author David Brownell
   133  * @version 1.1, 00/08/05
   134  * @see java.util.Locale
   135  * @see java.util.ListResourceBundle
   136  * @see java.text.MessageFormat
   137  */
   138 // leave this as "abstract" -- each package needs its own subclass,
   139 // else it's not always going to be using the right class loader.
   140 abstract public class MessageCatalog {
   141     private String bundleName;
   143     /**
   144      * Create a message catalog for use by classes in the same package
   145      * as the specified class.  This uses <em>Messages</em> resource
   146      * bundles in the <em>resources</em> sub-package of class passed as
   147      * a parameter.
   148      *
   149      * @param packageMember Class whose package has localized messages
   150      */
   151     protected MessageCatalog(Class packageMember) {
   152         this(packageMember, "Messages");
   153     }
   155     /**
   156      * Create a message catalog for use by classes in the same package
   157      * as the specified class.  This uses the specified resource
   158      * bundle name in the <em>resources</em> sub-package of class passed
   159      * as a parameter; for example, <em>resources.Messages</em>.
   160      *
   161      * @param packageMember Class whose package has localized messages
   162      * @param bundle        Name of a group of resource bundles
   163      */
   164     private MessageCatalog(Class packageMember, String bundle) {
   165         int index;
   167         bundleName = packageMember.getName();
   168         index = bundleName.lastIndexOf('.');
   169         if (index == -1)    // "ClassName"
   170             bundleName = "";
   171         else            // "some.package.ClassName"
   172             bundleName = bundleName.substring(0, index) + ".";
   173         bundleName = bundleName + "resources." + bundle;
   174     }
   177     /**
   178      * Get a message localized to the specified locale, using the message ID
   179      * and package name if no message is available.  The locale is normally
   180      * that of the client of a service, chosen with knowledge that both the
   181      * client and this server support that locale.  There are two error
   182      * cases:  first, when the specified locale is unsupported or null, the
   183      * default locale is used if possible; second, when no bundle supports
   184      * that locale, the message ID and package name are used.
   185      *
   186      * @param locale    The locale of the message to use.  If this is null,
   187      *                  the default locale will be used.
   188      * @param messageId The ID of the message to use.
   189      * @return The message, localized as described above.
   190      */
   191     public String getMessage(Locale locale,
   192                              String messageId) {
   193         ResourceBundle bundle;
   195         // cope with unsupported locale...
   196         if (locale == null)
   197             locale = Locale.getDefault();
   199         try {
   200             bundle = ResourceBundle.getBundle(bundleName, locale);
   201         } catch (MissingResourceException e) {
   202             bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH);
   203         }
   204         return bundle.getString(messageId);
   205     }
   208     /**
   209      * Format a message localized to the specified locale, using the message
   210      * ID with its package name if none is available.  The locale is normally
   211      * the client of a service, chosen with knowledge that both the client
   212      * server support that locale.  There are two error cases:  first, if the
   213      * specified locale is unsupported or null, the default locale is used if
   214      * possible; second, when no bundle supports that locale, the message ID
   215      * and package name are used.
   216      *
   217      * @param locale     The locale of the message to use.  If this is null,
   218      *                   the default locale will be used.
   219      * @param messageId  The ID of the message format to use.
   220      * @param parameters Used when formatting the message.  Objects in
   221      *                   this list are turned to strings if they are not Strings, Numbers,
   222      *                   or Dates (that is, if MessageFormat would treat them as errors).
   223      * @return The message, localized as described above.
   224      * @see java.text.MessageFormat
   225      */
   226     public String getMessage(Locale locale,
   227                              String messageId,
   228                              Object parameters []) {
   229         if (parameters == null)
   230             return getMessage(locale, messageId);
   232         // since most messages won't be tested (sigh), be friendly to
   233         // the inevitable developer errors of passing random data types
   234         // to the message formatting code.
   235         for (int i = 0; i < parameters.length; i++) {
   236             if (!(parameters[i] instanceof String)
   237                     && !(parameters[i] instanceof Number)
   238                     && !(parameters[i] instanceof java.util.Date)) {
   239                 if (parameters[i] == null)
   240                     parameters[i] = "(null)";
   241                 else
   242                     parameters[i] = parameters[i].toString();
   243             }
   244         }
   246         // similarly, cope with unsupported locale...
   247         if (locale == null)
   248             locale = Locale.getDefault();
   250         // get the appropriately localized MessageFormat object
   251         ResourceBundle bundle;
   252         MessageFormat format;
   254         try {
   255             bundle = ResourceBundle.getBundle(bundleName, locale);
   256         } catch (MissingResourceException e) {
   257             bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH);
   258             /*String retval;
   260             retval = packagePrefix (messageId);
   261             for (int i = 0; i < parameters.length; i++) {
   262             retval += ' ';
   263             retval += parameters [i];
   264             }
   265             return retval;*/
   266         }
   267         format = new MessageFormat(bundle.getString(messageId));
   268         format.setLocale(locale);
   270         // return the formatted message
   271         StringBuffer result = new StringBuffer();
   273         result = format.format(parameters, result, new FieldPosition(0));
   274         return result.toString();
   275     }
   278     /**
   279      * Chooses a client locale to use, using the first language specified in
   280      * the list that is supported by this catalog.  If none of the specified
   281      * languages is supported, a null value is returned.  Such a list of
   282      * languages might be provided in an HTTP/1.1 "Accept-Language" header
   283      * field, or through some other content negotiation mechanism.
   284      * <p/>
   285      * <P> The language specifiers recognized are RFC 1766 style ("fr" for
   286      * all French, "fr-ca" for Canadian French), although only the strict
   287      * ISO subset (two letter language and country specifiers) is currently
   288      * supported.  Java-style locale strings ("fr_CA") are also supported.
   289      *
   290      * @param languages Array of language specifiers, ordered with the most
   291      *                  preferable one at the front.  For example, "en-ca" then "fr-ca",
   292      *                  followed by "zh_CN".
   293      * @return The most preferable supported locale, or null.
   294      * @see java.util.Locale
   295      */
   296     public Locale chooseLocale(String languages []) {
   297         if ((languages = canonicalize(languages)) != null) {
   298             for (int i = 0; i < languages.length; i++)
   299                 if (isLocaleSupported(languages[i]))
   300                     return getLocale(languages[i]);
   301         }
   302         return null;
   303     }
   306     //
   307     // Canonicalizes the RFC 1766 style language strings ("en-in") to
   308     // match standard Java usage ("en_IN"), removing strings that don't
   309     // use two character ISO language and country codes.   Avoids all
   310     // memory allocations possible, so that if the strings passed in are
   311     // just lowercase ISO codes (a common case) the input is returned.
   312     //
   313     private String[] canonicalize(String languages []) {
   314         boolean didClone = false;
   315         int trimCount = 0;
   317         if (languages == null)
   318             return languages;
   320         for (int i = 0; i < languages.length; i++) {
   321             String lang = languages[i];
   322             int len = lang.length();
   324             // no RFC1766 extensions allowed; "zh" and "zh-tw" (etc) are OK
   325             // as are regular locale names with no variant ("de_CH").
   326             if (!(len == 2 || len == 5)) {
   327                 if (!didClone) {
   328                     languages = (String[]) languages.clone();
   329                     didClone = true;
   330                 }
   331                 languages[i] = null;
   332                 trimCount++;
   333                 continue;
   334             }
   336             // language code ... if already lowercase, we change nothing
   337             if (len == 2) {
   338                 lang = lang.toLowerCase();
   339                 if (lang != languages[i]) {
   340                     if (!didClone) {
   341                         languages = (String[]) languages.clone();
   342                         didClone = true;
   343                     }
   344                     languages[i] = lang;
   345                 }
   346                 continue;
   347             }
   349             // language_country ... fixup case, force "_"
   350             char buf [] = new char[5];
   352             buf[0] = Character.toLowerCase(lang.charAt(0));
   353             buf[1] = Character.toLowerCase(lang.charAt(1));
   354             buf[2] = '_';
   355             buf[3] = Character.toUpperCase(lang.charAt(3));
   356             buf[4] = Character.toUpperCase(lang.charAt(4));
   357             if (!didClone) {
   358                 languages = (String[]) languages.clone();
   359                 didClone = true;
   360             }
   361             languages[i] = new String(buf);
   362         }
   364         // purge any shadows of deleted RFC1766 extended language codes
   365         if (trimCount != 0) {
   366             String temp [] = new String[languages.length - trimCount];
   367             int i;
   369             for (i = 0, trimCount = 0; i < temp.length; i++) {
   370                 while (languages[i + trimCount] == null)
   371                     trimCount++;
   372                 temp[i] = languages[i + trimCount];
   373             }
   374             languages = temp;
   375         }
   376         return languages;
   377     }
   380     //
   381     // Returns a locale object supporting the specified locale, using
   382     // a small cache to speed up some common languages and reduce the
   383     // needless allocation of memory.
   384     //
   385     private Locale getLocale(String localeName) {
   386         String language, country;
   387         int index;
   389         index = localeName.indexOf('_');
   390         if (index == -1) {
   391             //
   392             // Special case the builtin JDK languages
   393             //
   394             if (localeName.equals("de"))
   395                 return Locale.GERMAN;
   396             if (localeName.equals("en"))
   397                 return Locale.ENGLISH;
   398             if (localeName.equals("fr"))
   399                 return Locale.FRENCH;
   400             if (localeName.equals("it"))
   401                 return Locale.ITALIAN;
   402             if (localeName.equals("ja"))
   403                 return Locale.JAPANESE;
   404             if (localeName.equals("ko"))
   405                 return Locale.KOREAN;
   406             if (localeName.equals("zh"))
   407                 return Locale.CHINESE;
   409             language = localeName;
   410             country = "";
   411         } else {
   412             if (localeName.equals("zh_CN"))
   413                 return Locale.SIMPLIFIED_CHINESE;
   414             if (localeName.equals("zh_TW"))
   415                 return Locale.TRADITIONAL_CHINESE;
   417             //
   418             // JDK also has constants for countries:  en_GB, en_US, en_CA,
   419             // fr_FR, fr_CA, de_DE, ja_JP, ko_KR.  We don't use those.
   420             //
   421             language = localeName.substring(0, index);
   422             country = localeName.substring(index + 1);
   423         }
   425         return new Locale(language, country);
   426     }
   429     //
   430     // cache for isLanguageSupported(), below ... key is a language
   431     // or locale name, value is a Boolean
   432     //
   433     private Hashtable cache = new Hashtable(5);
   436     /**
   437      * Returns true iff the specified locale has explicit language support.
   438      * For example, the traditional Chinese locale "zh_TW" has such support
   439      * if there are message bundles suffixed with either "zh_TW" or "zh".
   440      * <p/>
   441      * <P> This method is used to bypass part of the search path mechanism
   442      * of the <code>ResourceBundle</code> class, specifically the parts which
   443      * force use of default locales and bundles.  Such bypassing is required
   444      * in order to enable use of a client's preferred languages.  Following
   445      * the above example, if a client prefers "zh_TW" but can also accept
   446      * "ja", this method would be used to detect that there are no "zh_TW"
   447      * resource bundles and hence that "ja" messages should be used.  This
   448      * bypasses the ResourceBundle mechanism which will return messages in
   449      * some other locale (picking some hard-to-anticipate default) instead
   450      * of reporting an error and letting the client choose another locale.
   451      *
   452      * @param localeName A standard Java locale name, using two character
   453      *                   language codes optionally suffixed by country codes.
   454      * @return True iff the language of that locale is supported.
   455      * @see java.util.Locale
   456      */
   457     public boolean isLocaleSupported(String localeName) {
   458         //
   459         // Use previous results if possible.  We expect that the codebase
   460         // is immutable, so we never worry about changing the cache.
   461         //
   462         Boolean value = (Boolean) cache.get(localeName);
   464         if (value != null)
   465             return value.booleanValue();
   467         //
   468         // Try "language_country_variant", then "language_country",
   469         // then finally "language" ... assuming the longest locale name
   470         // is passed.  If not, we'll try fewer options.
   471         //
   472         ClassLoader loader = null;
   474         for (; ;) {
   475             String name = bundleName + "_" + localeName;
   477             // look up classes ...
   478             try {
   479                 Class.forName(name);
   480                 cache.put(localeName, Boolean.TRUE);
   481                 return true;
   482             } catch (Exception e) {
   483             }
   485             // ... then property files (only for ISO Latin/1 messages)
   486             InputStream in;
   488             if (loader == null)
   489                 loader = getClass().getClassLoader();
   491             name = name.replace('.', '/');
   492             name = name + ".properties";
   493             if (loader == null)
   494                 in = ClassLoader.getSystemResourceAsStream(name);
   495             else
   496                 in = loader.getResourceAsStream(name);
   497             if (in != null) {
   498                 cache.put(localeName, Boolean.TRUE);
   499                 return true;
   500             }
   502             int index = localeName.indexOf('_');
   504             if (index > 0)
   505                 localeName = localeName.substring(0, index);
   506             else
   507                 break;
   508         }
   510         //
   511         // If we got this far, we failed.  Remember for later.
   512         //
   513         cache.put(localeName, Boolean.FALSE);
   514         return false;
   515     }
   516 }

mercurial