001: /* Locales.java
002:
003: {{IS_NOTE
004:
005: Purpose:
006: Description:
007: History:
008: 2002/01/25 11:58:34, Create, Tom M. Yeh.
009: }}IS_NOTE
010:
011: Copyright (C) 2001 Potix Corporation. All Rights Reserved.
012:
013: {{IS_RIGHT
014: This program is distributed under GPL Version 2.0 in the hope that
015: it will be useful, but WITHOUT ANY WARRANTY.
016: }}IS_RIGHT
017: */
018: package org.zkoss.util;
019:
020: import java.util.LinkedList;
021: import java.util.Map;
022: import java.util.HashMap;
023: import java.util.Locale;
024: import java.util.Iterator;
025: import java.util.Collection;
026:
027: import org.zkoss.lang.D;
028: import org.zkoss.lang.Objects;
029:
030: /**
031: * The locale relevant utilities.
032: *
033: * @author tomyeh
034: */
035: public class Locales {
036: private final static InheritableThreadLocal _thdLocale = new InheritableThreadLocal();
037:
038: /** Returns the current locale; never null.
039: * This is the locale that every other objects shall use,
040: * unless they have special consideration.
041: *
042: * <p>Default: If {@link #setThreadLocal} was called with non-null,
043: * the value is returned. Otherwise, Locale.getDefault() is returned,
044: */
045: public static final Locale getCurrent() {
046: final Locale l = (Locale) _thdLocale.get();
047: return l != null ? l : Locale.getDefault();
048: }
049:
050: /** Returns whether the current locale ({@link #getCurrent}) belongs
051: * to the specified language and/or country.
052: *
053: * @param lang the language code, e.g., en and zh. Ignored if null.
054: * @param country the country code, e.g., US. Ignored if null.
055: * If empty, it means no country code at all.
056: */
057: public static final boolean testCurrent(String lang, String country) {
058: final Locale l = getCurrent();
059: return (lang == null || lang.equals(l.getLanguage()))
060: && (country == null || country.equals(l.getCountry()));
061: }
062:
063: /**
064: * Sets the locale for the current thread only.
065: *
066: * <p>Each thread could have an independent locale, called
067: * the thread locale.
068: *
069: * <p>When Invoking this method under a thread that serves requests,
070: * remember to clean up the setting upon completing each request.
071: *
072: * <pre><code>Locale old = Locales.setThreadLocal(newValue);
073: *try {
074: * ...
075: *} finally {
076: * Locales.setThreadLocal(old);
077: *}</code></pre>
078: *
079: * @param locale the thread locale; null to denote no thread locale
080: * @return the previous thread locale
081: */
082: public static final Locale setThreadLocal(Locale locale) {
083: final Locale old = (Locale) _thdLocale.get();
084: _thdLocale.set(locale);
085: return old;
086: }
087:
088: /**
089: * Returns the locale defined by {@link #setThreadLocal}.
090: *
091: * @since 3.0.0
092: * @see #getCurrent
093: */
094: public static final Locale getThreadLocal() {
095: return (Locale) _thdLocale.get();
096: }
097:
098: /** Converts a string that consists of language, country and variant
099: * to a locale.
100: *
101: * <p>The separator between language, country and variant
102: * is customizable, and whitespaces are ignored, e.g.,
103: * "zh_TW" and "zh, TW".
104: *
105: * <p>Thus, locale.equals(Locales.getLocale(locale.toString(), '_')).
106: *
107: * @param localeString the locale in string; null is OK
108: * @param separator the separator; ((char)0) means to decide automatically
109: * (either ',' or '_')
110: * @return the locale or null if locale is null or empty
111: */
112: public static final Locale getLocale(String localeString,
113: char separator) {
114: if (localeString == null)
115: return null;
116:
117: assert D.OFF || !localeString.equals("null") : "No Locale called 'null'"; //some caller not filter null out
118:
119: if (separator == (char) 0)
120: separator = localeString.indexOf('_') >= 0 ? '_' : ',';
121: LinkedList list = new LinkedList();
122: CollectionsX.parse(list, localeString, separator);
123:
124: String lang = "", cnt = "", var = "";
125: switch (list.size()) {
126: case 0:
127: return null;
128: default:
129: assert (list.size() <= 3);
130: var = (String) list.get(2);
131: case 2:
132: cnt = (String) list.get(1);
133: if (cnt.length() != 2)
134: throw new IllegalArgumentException(
135: "Not a valid country: " + localeString);
136: case 1:
137: lang = (String) list.get(0);
138: if (lang.length() != 2)
139: throw new IllegalArgumentException(
140: "Not a valid language: " + localeString);
141: }
142:
143: return getLocale(new Locale(lang, cnt, var));
144:
145: }
146:
147: /** Converts a string that consists of language, country and variant
148: * to a locale.
149: *
150: * <p>A shortcut: getLocale(localeString, (char)0).
151: */
152: public static final Locale getLocale(String localeString) {
153: return getLocale(localeString, (char) 0);
154: }
155:
156: /** Converts a Locale to one of them being used before.
157: * To save memory (since locale is used frequently), it is suggested
158: * to pass thru this method after creating a new instance of Locale.<br>
159: * Example, getLocale(new Locale(...)).
160: *
161: * <p>This method first look for any locale
162: */
163: synchronized public static final Locale getLocale(Locale locale) {
164: final Locale l = (Locale) _founds.get(locale);
165: if (l != null)
166: return l;
167:
168: _founds.put(locale, locale);
169: return locale;
170: }
171:
172: /** Locales that are found so far. */
173: private static final Map _founds = new HashMap(17);
174: static {
175: final Locale[] ls = new Locale[] { Locale.TRADITIONAL_CHINESE,
176: Locale.SIMPLIFIED_CHINESE, Locale.ENGLISH, Locale.US,
177: Locale.JAPAN, Locale.JAPANESE, Locale.KOREA,
178: Locale.KOREAN, Locale.FRANCE, Locale.FRENCH,
179: Locale.GERMANY, Locale.GERMAN, Locale.CHINESE };
180: for (int j = 0; j < ls.length; ++j)
181: _founds.put(ls[j], ls[j]);
182: }
183:
184: /** Returns any occurence of the specified Locale or any its fallback
185: * in the value collection, or null if not found.
186: * By fallback, we mean will try without variant and country.
187: * Example, if locale is zh_TW, it will try zh_TW and then zh.
188: */
189: public static Locale getByFallback(Collection values, Locale locale) {
190: if (match(values, locale))
191: return locale;
192:
193: final String lang = locale.getLanguage();
194: final String cnty = locale.getCountry();
195: final String var = locale.getVariant();
196: if (var != null && var.length() > 0) {
197: locale = new Locale(lang, cnty);
198: if (match(values, locale))
199: return locale;
200: }
201: if (cnty != null && cnty.length() > 0) {
202: locale = new Locale(lang, "");
203: if (match(values, locale))
204: return locale;
205: }
206:
207: //search the first one that matches partially
208: Locale rtn = null;
209: for (final Iterator it = values.iterator(); it.hasNext();) {
210: final Locale l = (Locale) it.next();
211: if (l.getLanguage().equals(lang)) {
212: //case 1: it matches all but the last element -> done
213: if (var == null || var.length() == 0
214: || Objects.equals(l.getCountry(), cnty))//country might null
215: return l;
216:
217: //case 2: it matches only language, we seeek for any case 1
218: if (rtn == null)
219: rtn = l;
220: }
221: }
222: return rtn;
223: }
224:
225: /** Tests whether a locale exists in the values collection. */
226: private static boolean match(Collection values, Locale locale) {
227: for (final Iterator it = values.iterator(); it.hasNext();)
228: if (locale.equals(it.next()))
229: return true;
230: return false;
231: }
232:
233: /** Returns the index of '_' preceding the country part, starting from j.
234: * It is similar to s.indexOf('_', j), except it detects country part
235: * (which must be only two letter in lower cases.
236: */
237: public static int indexOfUnderline(String s, int j) {
238: int k, last = s.length() - 2;
239: for (; (k = s.indexOf('_', j)) >= 0 && k < last; j = k + 1) {
240: char cc = s.charAt(k + 1);
241: if (cc < 'a' || cc > 'z')
242: continue; //not found
243: cc = s.charAt(k + 2);
244: if (cc < 'a' || cc > 'z')
245: continue; //not found
246: cc = s.charAt(k + 3);
247: if (cc < 'a' || cc > 'z')
248: return k; //found
249: }
250: return -1;
251: }
252:
253: /** Returns the index of '_' preceding the country part.
254: * It is similar to s.indexOf('_'), except it detects country part
255: * (which must be only two letter in lower cases.
256: */
257: public static int indexOfUnderline(String s) {
258: return indexOfUnderline(s, 0);
259: }
260: }
|