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

changeset 0
373ffda63c9a
child 637
9c07ef4934dd
equal deleted inserted replaced
-1:000000000000 0:373ffda63c9a
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 */
25
26 package com.sun.xml.internal.dtdparser;
27
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;
35
36
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;
142
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 }
154
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;
166
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 }
175
176
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;
194
195 // cope with unsupported locale...
196 if (locale == null)
197 locale = Locale.getDefault();
198
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 }
206
207
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);
231
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 }
245
246 // similarly, cope with unsupported locale...
247 if (locale == null)
248 locale = Locale.getDefault();
249
250 // get the appropriately localized MessageFormat object
251 ResourceBundle bundle;
252 MessageFormat format;
253
254 try {
255 bundle = ResourceBundle.getBundle(bundleName, locale);
256 } catch (MissingResourceException e) {
257 bundle = ResourceBundle.getBundle(bundleName, Locale.ENGLISH);
258 /*String retval;
259
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);
269
270 // return the formatted message
271 StringBuffer result = new StringBuffer();
272
273 result = format.format(parameters, result, new FieldPosition(0));
274 return result.toString();
275 }
276
277
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 }
304
305
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;
316
317 if (languages == null)
318 return languages;
319
320 for (int i = 0; i < languages.length; i++) {
321 String lang = languages[i];
322 int len = lang.length();
323
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 }
335
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 }
348
349 // language_country ... fixup case, force "_"
350 char buf [] = new char[5];
351
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 }
363
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;
368
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 }
378
379
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;
388
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;
408
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;
416
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 }
424
425 return new Locale(language, country);
426 }
427
428
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);
434
435
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);
463
464 if (value != null)
465 return value.booleanValue();
466
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;
473
474 for (; ;) {
475 String name = bundleName + "_" + localeName;
476
477 // look up classes ...
478 try {
479 Class.forName(name);
480 cache.put(localeName, Boolean.TRUE);
481 return true;
482 } catch (Exception e) {
483 }
484
485 // ... then property files (only for ISO Latin/1 messages)
486 InputStream in;
487
488 if (loader == null)
489 loader = getClass().getClassLoader();
490
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 }
501
502 int index = localeName.indexOf('_');
503
504 if (index > 0)
505 localeName = localeName.substring(0, index);
506 else
507 break;
508 }
509
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