1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/dtdparser/MessageCatalog.java Tue Mar 06 16:09:35 2012 -0800 1.3 @@ -0,0 +1,516 @@ 1.4 +/* 1.5 + * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Oracle designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Oracle in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.25 + * or visit www.oracle.com if you need additional information or have any 1.26 + * questions. 1.27 + */ 1.28 + 1.29 +package com.sun.xml.internal.dtdparser; 1.30 + 1.31 +import java.io.InputStream; 1.32 +import java.text.FieldPosition; 1.33 +import java.text.MessageFormat; 1.34 +import java.util.Hashtable; 1.35 +import java.util.Locale; 1.36 +import java.util.MissingResourceException; 1.37 +import java.util.ResourceBundle; 1.38 + 1.39 + 1.40 +/** 1.41 + * This class provides support for multi-language string lookup, as needed 1.42 + * to localize messages from applications supporting multiple languages 1.43 + * at the same time. One class of such applications is network services, 1.44 + * such as HTTP servers, which talk to clients who may not be from the 1.45 + * same locale as the server. This class supports a form of negotiation 1.46 + * for the language used in presenting a message from some package, where 1.47 + * both user (client) preferences and application (server) support are 1.48 + * accounted for when choosing locales and formatting messages. 1.49 + * <p/> 1.50 + * <P> Each package should have a singleton package-private message catalog 1.51 + * class. This ensures that the correct class loader will always be used to 1.52 + * access message resources, and minimizes use of memory: <PRE> 1.53 + * package <em>some.package</em>; 1.54 + * <p/> 1.55 + * // "foo" might be public 1.56 + * class foo { 1.57 + * ... 1.58 + * // package private 1.59 + * static final Catalog messages = new Catalog (); 1.60 + * static final class Catalog extends MessageCatalog { 1.61 + * Catalog () { super (Catalog.class); } 1.62 + * } 1.63 + * ... 1.64 + * } 1.65 + * </PRE> 1.66 + * <p/> 1.67 + * <P> Messages for a known client could be generated using code 1.68 + * something like this: <PRE> 1.69 + * String clientLanguages []; 1.70 + * Locale clientLocale; 1.71 + * String clientMessage; 1.72 + * <p/> 1.73 + * // client languages will probably be provided by client, 1.74 + * // e.g. by an HTTP/1.1 "Accept-Language" header. 1.75 + * clientLanguages = new String [] { "en-ca", "fr-ca", "ja", "zh" }; 1.76 + * clientLocale = foo.messages.chooseLocale (clientLanguages); 1.77 + * clientMessage = foo.messages.getMessage (clientLocale, 1.78 + * "fileCount", 1.79 + * new Object [] { new Integer (numberOfFiles) } 1.80 + * ); 1.81 + * </PRE> 1.82 + * <p/> 1.83 + * <P> At this time, this class does not include functionality permitting 1.84 + * messages to be passed around and localized after-the-fact. The consequence 1.85 + * of this is that the locale for messages must be passed down through layers 1.86 + * which have no normal reason to support such passdown, or else the system 1.87 + * default locale must be used instead of the one the client needs. 1.88 + * <p/> 1.89 + * <P> <hr> The following guidelines should be used when constructiong 1.90 + * multi-language applications: <OL> 1.91 + * <p/> 1.92 + * <LI> Always use <a href=#chooseLocale>chooseLocale</a> to select the 1.93 + * locale you pass to your <code>getMessage</code> call. This lets your 1.94 + * applications use IETF standard locale names, and avoids needless 1.95 + * use of system defaults. 1.96 + * <p/> 1.97 + * <LI> The localized messages for a given package should always go in 1.98 + * a separate <em>resources</em> sub-package. There are security 1.99 + * implications; see below. 1.100 + * <p/> 1.101 + * <LI> Make sure that a language name is included in each bundle name, 1.102 + * so that the developer's locale will not be inadvertently used. That 1.103 + * is, don't create defaults like <em>resources/Messages.properties</em> 1.104 + * or <em>resources/Messages.class</em>, since ResourceBundle will choose 1.105 + * such defaults rather than giving software a chance to choose a more 1.106 + * appropriate language for its messages. Your message bundles should 1.107 + * have names like <em>Messages_en.properties</em> (for the "en", or 1.108 + * English, language) or <em>Messages_ja.class</em> ("ja" indicates the 1.109 + * Japanese language). 1.110 + * <p/> 1.111 + * <LI> Only use property files for messages in languages which can 1.112 + * be limited to the ISO Latin/1 (8859-1) characters supported by the 1.113 + * property file format. (This is mostly Western European languages.) 1.114 + * Otherwise, subclass ResourceBundle to provide your messages; it is 1.115 + * simplest to subclass <code>java.util.ListResourceBundle</code>. 1.116 + * <p/> 1.117 + * <LI> Never use another package's message catalog or resource bundles. 1.118 + * It should not be possible for a change internal to one package (such 1.119 + * as eliminating or improving messages) to break another package. 1.120 + * <p/> 1.121 + * </OL> 1.122 + * <p/> 1.123 + * <P> The "resources" sub-package can be treated separately from the 1.124 + * package with which it is associated. That main package may be sealed 1.125 + * and possibly signed, preventing other software from adding classes to 1.126 + * the package which would be able to access methods and data which are 1.127 + * not designed to be publicly accessible. On the other hand, resources 1.128 + * such as localized messages are often provided after initial product 1.129 + * shipment, without a full release cycle for the product. Such files 1.130 + * (text and class files) need to be added to some package. Since they 1.131 + * should not be added to the main package, the "resources" subpackage is 1.132 + * used without risking the security or integrity of that main package 1.133 + * as distributed in its JAR file. 1.134 + * 1.135 + * @author David Brownell 1.136 + * @version 1.1, 00/08/05 1.137 + * @see java.util.Locale 1.138 + * @see java.util.ListResourceBundle 1.139 + * @see java.text.MessageFormat 1.140 + */ 1.141 +// leave this as "abstract" -- each package needs its own subclass, 1.142 +// else it's not always going to be using the right class loader. 1.143 +abstract public class MessageCatalog { 1.144 + private String bundleName; 1.145 + 1.146 + /** 1.147 + * Create a message catalog for use by classes in the same package 1.148 + * as the specified class. This uses <em>Messages</em> resource 1.149 + * bundles in the <em>resources</em> sub-package of class passed as 1.150 + * a parameter. 1.151 + * 1.152 + * @param packageMember Class whose package has localized messages 1.153 + */ 1.154 + protected MessageCatalog(Class packageMember) { 1.155 + this(packageMember, "Messages"); 1.156 + } 1.157 + 1.158 + /** 1.159 + * Create a message catalog for use by classes in the same package 1.160 + * as the specified class. This uses the specified resource 1.161 + * bundle name in the <em>resources</em> sub-package of class passed 1.162 + * as a parameter; for example, <em>resources.Messages</em>. 1.163 + * 1.164 + * @param packageMember Class whose package has localized messages 1.165 + * @param bundle Name of a group of resource bundles 1.166 + */ 1.167 + private MessageCatalog(Class packageMember, String bundle) { 1.168 + int index; 1.169 + 1.170 + bundleName = packageMember.getName(); 1.171 + index = bundleName.lastIndexOf('.'); 1.172 + if (index == -1) // "ClassName" 1.173 + bundleName = ""; 1.174 + else // "some.package.ClassName" 1.175 + bundleName = bundleName.substring(0, index) + "."; 1.176 + bundleName = bundleName + "resources." + bundle; 1.177 + } 1.178 + 1.179 + 1.180 + /** 1.181 + * Get a message localized to the specified locale, using the message ID 1.182 + * and package name if no message is available. The locale is normally 1.183 + * that of the client of a service, chosen with knowledge that both the 1.184 + * client and this server support that locale. There are two error 1.185 + * cases: first, when the specified locale is unsupported or null, the 1.186 + * default locale is used if possible; second, when no bundle supports 1.187 + * that locale, the message ID and package name are used. 1.188 + * 1.189 + * @param locale The locale of the message to use. If this is null, 1.190 + * the default locale will be used. 1.191 + * @param messageId The ID of the message to use. 1.192 + * @return The message, localized as described above. 1.193 + */ 1.194 + public String getMessage(Locale locale, 1.195 + String messageId) { 1.196 + ResourceBundle bundle; 1.197 + 1.198 + // cope with unsupported locale... 1.199 + if (locale == null) 1.200 + locale = Locale.getDefault(); 1.201 + 1.202 + try { 1.203 + bundle = ResourceBundle.getBundle(bundleName, locale); 1.204 + } catch (MissingResourceException e) { 1.205 + bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH); 1.206 + } 1.207 + return bundle.getString(messageId); 1.208 + } 1.209 + 1.210 + 1.211 + /** 1.212 + * Format a message localized to the specified locale, using the message 1.213 + * ID with its package name if none is available. The locale is normally 1.214 + * the client of a service, chosen with knowledge that both the client 1.215 + * server support that locale. There are two error cases: first, if the 1.216 + * specified locale is unsupported or null, the default locale is used if 1.217 + * possible; second, when no bundle supports that locale, the message ID 1.218 + * and package name are used. 1.219 + * 1.220 + * @param locale The locale of the message to use. If this is null, 1.221 + * the default locale will be used. 1.222 + * @param messageId The ID of the message format to use. 1.223 + * @param parameters Used when formatting the message. Objects in 1.224 + * this list are turned to strings if they are not Strings, Numbers, 1.225 + * or Dates (that is, if MessageFormat would treat them as errors). 1.226 + * @return The message, localized as described above. 1.227 + * @see java.text.MessageFormat 1.228 + */ 1.229 + public String getMessage(Locale locale, 1.230 + String messageId, 1.231 + Object parameters []) { 1.232 + if (parameters == null) 1.233 + return getMessage(locale, messageId); 1.234 + 1.235 + // since most messages won't be tested (sigh), be friendly to 1.236 + // the inevitable developer errors of passing random data types 1.237 + // to the message formatting code. 1.238 + for (int i = 0; i < parameters.length; i++) { 1.239 + if (!(parameters[i] instanceof String) 1.240 + && !(parameters[i] instanceof Number) 1.241 + && !(parameters[i] instanceof java.util.Date)) { 1.242 + if (parameters[i] == null) 1.243 + parameters[i] = "(null)"; 1.244 + else 1.245 + parameters[i] = parameters[i].toString(); 1.246 + } 1.247 + } 1.248 + 1.249 + // similarly, cope with unsupported locale... 1.250 + if (locale == null) 1.251 + locale = Locale.getDefault(); 1.252 + 1.253 + // get the appropriately localized MessageFormat object 1.254 + ResourceBundle bundle; 1.255 + MessageFormat format; 1.256 + 1.257 + try { 1.258 + bundle = ResourceBundle.getBundle(bundleName, locale); 1.259 + } catch (MissingResourceException e) { 1.260 + bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH); 1.261 + /*String retval; 1.262 + 1.263 + retval = packagePrefix (messageId); 1.264 + for (int i = 0; i < parameters.length; i++) { 1.265 + retval += ' '; 1.266 + retval += parameters [i]; 1.267 + } 1.268 + return retval;*/ 1.269 + } 1.270 + format = new MessageFormat(bundle.getString(messageId)); 1.271 + format.setLocale(locale); 1.272 + 1.273 + // return the formatted message 1.274 + StringBuffer result = new StringBuffer(); 1.275 + 1.276 + result = format.format(parameters, result, new FieldPosition(0)); 1.277 + return result.toString(); 1.278 + } 1.279 + 1.280 + 1.281 + /** 1.282 + * Chooses a client locale to use, using the first language specified in 1.283 + * the list that is supported by this catalog. If none of the specified 1.284 + * languages is supported, a null value is returned. Such a list of 1.285 + * languages might be provided in an HTTP/1.1 "Accept-Language" header 1.286 + * field, or through some other content negotiation mechanism. 1.287 + * <p/> 1.288 + * <P> The language specifiers recognized are RFC 1766 style ("fr" for 1.289 + * all French, "fr-ca" for Canadian French), although only the strict 1.290 + * ISO subset (two letter language and country specifiers) is currently 1.291 + * supported. Java-style locale strings ("fr_CA") are also supported. 1.292 + * 1.293 + * @param languages Array of language specifiers, ordered with the most 1.294 + * preferable one at the front. For example, "en-ca" then "fr-ca", 1.295 + * followed by "zh_CN". 1.296 + * @return The most preferable supported locale, or null. 1.297 + * @see java.util.Locale 1.298 + */ 1.299 + public Locale chooseLocale(String languages []) { 1.300 + if ((languages = canonicalize(languages)) != null) { 1.301 + for (int i = 0; i < languages.length; i++) 1.302 + if (isLocaleSupported(languages[i])) 1.303 + return getLocale(languages[i]); 1.304 + } 1.305 + return null; 1.306 + } 1.307 + 1.308 + 1.309 + // 1.310 + // Canonicalizes the RFC 1766 style language strings ("en-in") to 1.311 + // match standard Java usage ("en_IN"), removing strings that don't 1.312 + // use two character ISO language and country codes. Avoids all 1.313 + // memory allocations possible, so that if the strings passed in are 1.314 + // just lowercase ISO codes (a common case) the input is returned. 1.315 + // 1.316 + private String[] canonicalize(String languages []) { 1.317 + boolean didClone = false; 1.318 + int trimCount = 0; 1.319 + 1.320 + if (languages == null) 1.321 + return languages; 1.322 + 1.323 + for (int i = 0; i < languages.length; i++) { 1.324 + String lang = languages[i]; 1.325 + int len = lang.length(); 1.326 + 1.327 + // no RFC1766 extensions allowed; "zh" and "zh-tw" (etc) are OK 1.328 + // as are regular locale names with no variant ("de_CH"). 1.329 + if (!(len == 2 || len == 5)) { 1.330 + if (!didClone) { 1.331 + languages = (String[]) languages.clone(); 1.332 + didClone = true; 1.333 + } 1.334 + languages[i] = null; 1.335 + trimCount++; 1.336 + continue; 1.337 + } 1.338 + 1.339 + // language code ... if already lowercase, we change nothing 1.340 + if (len == 2) { 1.341 + lang = lang.toLowerCase(); 1.342 + if (lang != languages[i]) { 1.343 + if (!didClone) { 1.344 + languages = (String[]) languages.clone(); 1.345 + didClone = true; 1.346 + } 1.347 + languages[i] = lang; 1.348 + } 1.349 + continue; 1.350 + } 1.351 + 1.352 + // language_country ... fixup case, force "_" 1.353 + char buf [] = new char[5]; 1.354 + 1.355 + buf[0] = Character.toLowerCase(lang.charAt(0)); 1.356 + buf[1] = Character.toLowerCase(lang.charAt(1)); 1.357 + buf[2] = '_'; 1.358 + buf[3] = Character.toUpperCase(lang.charAt(3)); 1.359 + buf[4] = Character.toUpperCase(lang.charAt(4)); 1.360 + if (!didClone) { 1.361 + languages = (String[]) languages.clone(); 1.362 + didClone = true; 1.363 + } 1.364 + languages[i] = new String(buf); 1.365 + } 1.366 + 1.367 + // purge any shadows of deleted RFC1766 extended language codes 1.368 + if (trimCount != 0) { 1.369 + String temp [] = new String[languages.length - trimCount]; 1.370 + int i; 1.371 + 1.372 + for (i = 0, trimCount = 0; i < temp.length; i++) { 1.373 + while (languages[i + trimCount] == null) 1.374 + trimCount++; 1.375 + temp[i] = languages[i + trimCount]; 1.376 + } 1.377 + languages = temp; 1.378 + } 1.379 + return languages; 1.380 + } 1.381 + 1.382 + 1.383 + // 1.384 + // Returns a locale object supporting the specified locale, using 1.385 + // a small cache to speed up some common languages and reduce the 1.386 + // needless allocation of memory. 1.387 + // 1.388 + private Locale getLocale(String localeName) { 1.389 + String language, country; 1.390 + int index; 1.391 + 1.392 + index = localeName.indexOf('_'); 1.393 + if (index == -1) { 1.394 + // 1.395 + // Special case the builtin JDK languages 1.396 + // 1.397 + if (localeName.equals("de")) 1.398 + return Locale.GERMAN; 1.399 + if (localeName.equals("en")) 1.400 + return Locale.ENGLISH; 1.401 + if (localeName.equals("fr")) 1.402 + return Locale.FRENCH; 1.403 + if (localeName.equals("it")) 1.404 + return Locale.ITALIAN; 1.405 + if (localeName.equals("ja")) 1.406 + return Locale.JAPANESE; 1.407 + if (localeName.equals("ko")) 1.408 + return Locale.KOREAN; 1.409 + if (localeName.equals("zh")) 1.410 + return Locale.CHINESE; 1.411 + 1.412 + language = localeName; 1.413 + country = ""; 1.414 + } else { 1.415 + if (localeName.equals("zh_CN")) 1.416 + return Locale.SIMPLIFIED_CHINESE; 1.417 + if (localeName.equals("zh_TW")) 1.418 + return Locale.TRADITIONAL_CHINESE; 1.419 + 1.420 + // 1.421 + // JDK also has constants for countries: en_GB, en_US, en_CA, 1.422 + // fr_FR, fr_CA, de_DE, ja_JP, ko_KR. We don't use those. 1.423 + // 1.424 + language = localeName.substring(0, index); 1.425 + country = localeName.substring(index + 1); 1.426 + } 1.427 + 1.428 + return new Locale(language, country); 1.429 + } 1.430 + 1.431 + 1.432 + // 1.433 + // cache for isLanguageSupported(), below ... key is a language 1.434 + // or locale name, value is a Boolean 1.435 + // 1.436 + private Hashtable cache = new Hashtable(5); 1.437 + 1.438 + 1.439 + /** 1.440 + * Returns true iff the specified locale has explicit language support. 1.441 + * For example, the traditional Chinese locale "zh_TW" has such support 1.442 + * if there are message bundles suffixed with either "zh_TW" or "zh". 1.443 + * <p/> 1.444 + * <P> This method is used to bypass part of the search path mechanism 1.445 + * of the <code>ResourceBundle</code> class, specifically the parts which 1.446 + * force use of default locales and bundles. Such bypassing is required 1.447 + * in order to enable use of a client's preferred languages. Following 1.448 + * the above example, if a client prefers "zh_TW" but can also accept 1.449 + * "ja", this method would be used to detect that there are no "zh_TW" 1.450 + * resource bundles and hence that "ja" messages should be used. This 1.451 + * bypasses the ResourceBundle mechanism which will return messages in 1.452 + * some other locale (picking some hard-to-anticipate default) instead 1.453 + * of reporting an error and letting the client choose another locale. 1.454 + * 1.455 + * @param localeName A standard Java locale name, using two character 1.456 + * language codes optionally suffixed by country codes. 1.457 + * @return True iff the language of that locale is supported. 1.458 + * @see java.util.Locale 1.459 + */ 1.460 + public boolean isLocaleSupported(String localeName) { 1.461 + // 1.462 + // Use previous results if possible. We expect that the codebase 1.463 + // is immutable, so we never worry about changing the cache. 1.464 + // 1.465 + Boolean value = (Boolean) cache.get(localeName); 1.466 + 1.467 + if (value != null) 1.468 + return value.booleanValue(); 1.469 + 1.470 + // 1.471 + // Try "language_country_variant", then "language_country", 1.472 + // then finally "language" ... assuming the longest locale name 1.473 + // is passed. If not, we'll try fewer options. 1.474 + // 1.475 + ClassLoader loader = null; 1.476 + 1.477 + for (; ;) { 1.478 + String name = bundleName + "_" + localeName; 1.479 + 1.480 + // look up classes ... 1.481 + try { 1.482 + Class.forName(name); 1.483 + cache.put(localeName, Boolean.TRUE); 1.484 + return true; 1.485 + } catch (Exception e) { 1.486 + } 1.487 + 1.488 + // ... then property files (only for ISO Latin/1 messages) 1.489 + InputStream in; 1.490 + 1.491 + if (loader == null) 1.492 + loader = getClass().getClassLoader(); 1.493 + 1.494 + name = name.replace('.', '/'); 1.495 + name = name + ".properties"; 1.496 + if (loader == null) 1.497 + in = ClassLoader.getSystemResourceAsStream(name); 1.498 + else 1.499 + in = loader.getResourceAsStream(name); 1.500 + if (in != null) { 1.501 + cache.put(localeName, Boolean.TRUE); 1.502 + return true; 1.503 + } 1.504 + 1.505 + int index = localeName.indexOf('_'); 1.506 + 1.507 + if (index > 0) 1.508 + localeName = localeName.substring(0, index); 1.509 + else 1.510 + break; 1.511 + } 1.512 + 1.513 + // 1.514 + // If we got this far, we failed. Remember for later. 1.515 + // 1.516 + cache.put(localeName, Boolean.FALSE); 1.517 + return false; 1.518 + } 1.519 +}