KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > oreilly > servlet > LocaleNegotiator


1 // Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
2
// All rights reserved. Use of this class is limited.
3
// Please see the LICENSE for more information.
4

5 package com.oreilly.servlet;
6
7 import java.io.*;
8 import java.util.*;
9
10 import com.oreilly.servlet.LocaleToCharsetMap;
11
12 /**
13  * A class to aid in servlet internationalization. It determines, from a
14  * client request, the best charset, locale, and resource bundle to use
15  * with the response.
16  * <p>
17  * LocaleNegotiator works by scanning through the client's language
18  * preferences (sent by browsers in the <tt>Accept-Language</tt> header)
19  * looking for any
20  * language for which there exists is a corresponding resource bundle.
21  * When it finds a correspondence, it uses the LocaleToCharsetMap class
22  * to determine the charset. If there's any problem, it tries to fall
23  * back to US English. The logic currently ignores the client's charset
24  * preferences (sent in the <tt>Accept-Charset</tt> header).
25  * <p>
26  * It can be used like this:
27  * <blockquote><pre>
28  * String bundleName = "BundleName";
29  * String acceptLanguage = req.getHeader("Accept-Language");
30  * String acceptCharset = req.getHeader("Accept-Charset");
31  * &nbsp;
32  * LocaleNegotiator negotiator =
33  * new LocaleNegotiator(bundleName, acceptLanguage, acceptCharset);
34  * &nbsp;
35  * Locale locale = negotiator.getLocale();
36  * String charset = negotiator.getCharset();
37  * ResourceBundle bundle = negotiator.getBundle(); // may be null
38  * &nbsp;
39  * res.setContentType("text/plain; charset=" + charset);
40  * res.setHeader("Content-Language", locale.getLanguage());
41  * res.setHeader("Vary", "Accept-Language");
42  * &nbsp;
43  * PrintWriter out = res.getWriter();
44  * &nbsp;
45  * out.println(bundle.getString("resource"));
46  * </pre></blockquote>
47  *
48  * @see com.oreilly.servlet.LocaleToCharsetMap
49  *
50  * @author <b>Jason Hunter</b>, Copyright &#169; 1998
51  * @version 1.0, 98/09/18
52  */

53 public class LocaleNegotiator {
54
55   private ResourceBundle chosenBundle;
56   private Locale chosenLocale;
57   private String JavaDoc chosenCharset;
58
59   /**
60    * Constructs a new LocaleNegotiator for the given bundle name, language
61    * list, and charset list.
62    *
63    * @param bundleName the resource bundle name
64    * @param languages the Accept-Language header
65    * @param charsets the Accept-Charset header
66    */

67   public LocaleNegotiator(String JavaDoc bundleName,
68                           String JavaDoc languages,
69                           String JavaDoc charsets) {
70
71     // Specify default values:
72
// English language, ISO-8859-1 (Latin-1) charset, English bundle
73
Locale defaultLocale = new Locale("en", "US");
74     String JavaDoc defaultCharset = "ISO-8859-1";
75     ResourceBundle defaultBundle = null;
76     try {
77       defaultBundle = ResourceBundle.getBundle(bundleName, defaultLocale);
78     }
79     catch (MissingResourceException e) {
80       // No default bundle was found. Flying without a net.
81
}
82
83     // If the client didn't specify acceptable languages, we can keep
84
// the defaults.
85
if (languages == null) {
86       chosenLocale = defaultLocale;
87       chosenCharset = defaultCharset;
88       chosenBundle = defaultBundle;
89       return; // quick exit
90
}
91
92     // Use a tokenizer to separate acceptable languages
93
StringTokenizer tokenizer = new StringTokenizer(languages, ",");
94
95     while (tokenizer.hasMoreTokens()) {
96       // Get the next acceptable language.
97
// (The language can look something like "en; qvalue=0.91")
98
String JavaDoc lang = tokenizer.nextToken();
99
100       // Get the locale for that language
101
Locale loc = getLocaleForLanguage(lang);
102
103       // Get the bundle for this locale. Don't let the search fallback
104
// to match other languages!
105
ResourceBundle bundle = getBundleNoFallback(bundleName, loc);
106
107       // The returned bundle is null if there's no match. In that case
108
// we can't use this language since the servlet can't speak it.
109
if (bundle == null) continue; // on to the next language
110

111       // Find a charset we can use to display that locale's language.
112
String JavaDoc charset = getCharsetForLocale(loc, charsets);
113
114       // The returned charset is null if there's no match. In that case
115
// we can't use this language since the servlet can't encode it.
116
if (charset == null) continue; // on to the next language
117

118       // If we get here, there are no problems with this language.
119
chosenLocale = loc;
120       chosenBundle = bundle;
121       chosenCharset = charset;
122       return; // we're done
123
}
124
125     // No matches, so we let the defaults stand
126
chosenLocale = defaultLocale;
127     chosenCharset = defaultCharset;
128     chosenBundle = defaultBundle;
129   }
130
131   /**
132    * Gets the chosen bundle.
133    *
134    * @return the chosen bundle
135    */

136   public ResourceBundle getBundle() {
137     return chosenBundle;
138   }
139
140   /**
141    * Gets the chosen locale.
142    *
143    * @return the chosen locale
144    */

145   public Locale getLocale() {
146     return chosenLocale;
147   }
148
149   /**
150    * Gets the chosen charset.
151    *
152    * @return the chosen charset
153    */

154   public String JavaDoc getCharset() {
155     return chosenCharset;
156   }
157
158   /*
159    * Gets a Locale object for a given language string
160    */

161   private Locale getLocaleForLanguage(String JavaDoc lang) {
162     Locale loc;
163     int semi, dash;
164
165     // Cut off any qvalue that might come after a semi-colon
166
if ((semi = lang.indexOf(';')) != -1) {
167       lang = lang.substring(0, semi);
168     }
169
170     // Trim any whitespace
171
lang = lang.trim();
172
173     // Create a Locale from the language. A dash may separate the
174
// language from the country.
175
if ((dash = lang.indexOf('-')) == -1) {
176       loc = new Locale(lang, ""); // No dash, no country
177
}
178     else {
179       loc = new Locale(lang.substring(0, dash), lang.substring(dash+1));
180     }
181
182     return loc;
183   }
184
185   /*
186    * Gets a ResourceBundle object for the given bundle name and locale,
187    * or null if the bundle can't be found. The resource bundle must match
188    * the locale exactly. Fallback matches are not permitted.
189    */

190   private ResourceBundle getBundleNoFallback(String JavaDoc bundleName, Locale loc) {
191
192     // First get the fallback bundle -- the bundle that will be selected
193
// if getBundle() can't find a direct match. This bundle can be
194
// compared to the bundles returned by later calls to getBundle() in
195
// order to detect when getBundle() finds a direct match.
196
ResourceBundle fallback = null;
197     try {
198       fallback =
199         ResourceBundle.getBundle(bundleName, new Locale("bogus", ""));
200     }
201     catch (MissingResourceException e) {
202       // No fallback bundle was found.
203
}
204
205     try {
206       // Get the bundle for the specified locale
207
ResourceBundle bundle = ResourceBundle.getBundle(bundleName, loc);
208
209       // Is the bundle different than our fallback bundle?
210
if (bundle != fallback) {
211         // We have a real match!
212
return bundle;
213       }
214       // So the bundle is the same as our fallback bundle.
215
// We can still have a match, but only if our locale's language
216
// matches the default locale's language.
217
else if (bundle == fallback &&
218             loc.getLanguage().equals(Locale.getDefault().getLanguage())) {
219         // Another way to match
220
return bundle;
221       }
222       else {
223         // No match, keep looking
224
}
225     }
226     catch (MissingResourceException e) {
227       // No bundle available for this locale
228
}
229
230     return null; // no match
231
}
232
233   /**
234    * Gets the best charset for a given locale, selecting from a charset list.
235    * Currently ignores the charset list. Subclasses can override this
236    * method to take the list into account.
237    *
238    * @param loc the locale
239    * @param charsets a comma-separated charset list
240    * @return the best charset for the given locale from the given list
241    */

242   protected String JavaDoc getCharsetForLocale(Locale loc, String JavaDoc charsets) {
243     // Note: This method ignores the client-specified charsets
244
return LocaleToCharsetMap.getCharset(loc);
245   }
246 }
247
Popular Tags