001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.lang;
018:
019: import java.util.ArrayList;
020: import java.util.Arrays;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.List;
025: import java.util.Locale;
026: import java.util.Map;
027: import java.util.Set;
028:
029: /**
030: * <p>Operations to assist when working with a {@link Locale}.</p>
031: *
032: * <p>This class tries to handle <code>null</code> input gracefully.
033: * An exception will not be thrown for a <code>null</code> input.
034: * Each method documents its behaviour in more detail.</p>
035: *
036: * @author Stephen Colebourne
037: * @since 2.2
038: * @version $Id: LocaleUtils.java 489749 2006-12-22 20:34:37Z bayard $
039: */
040: public class LocaleUtils {
041:
042: /** Unmodifiable list of available locales. */
043: private static final List cAvailableLocaleList;
044: /** Unmodifiable set of available locales. */
045: private static Set cAvailableLocaleSet;
046: /** Unmodifiable map of language locales by country. */
047: private static final Map cLanguagesByCountry = Collections
048: .synchronizedMap(new HashMap());
049: /** Unmodifiable map of country locales by language. */
050: private static final Map cCountriesByLanguage = Collections
051: .synchronizedMap(new HashMap());
052: static {
053: List list = Arrays.asList(Locale.getAvailableLocales());
054: cAvailableLocaleList = Collections.unmodifiableList(list);
055: }
056:
057: /**
058: * <p><code>LocaleUtils</code> instances should NOT be constructed in standard programming.
059: * Instead, the class should be used as <code>LocaleUtils.toLocale("en_GB");</code>.</p>
060: *
061: * <p>This constructor is public to permit tools that require a JavaBean instance
062: * to operate.</p>
063: */
064: public LocaleUtils() {
065: super ();
066: }
067:
068: //-----------------------------------------------------------------------
069: /**
070: * <p>Converts a String to a Locale.</p>
071: *
072: * <p>This method takes the string format of a locale and creates the
073: * locale object from it.</p>
074: *
075: * <pre>
076: * LocaleUtils.toLocale("en") = new Locale("en", "")
077: * LocaleUtils.toLocale("en_GB") = new Locale("en", "GB")
078: * LocaleUtils.toLocale("en_GB_xxx") = new Locale("en", "GB", "xxx") (#)
079: * </pre>
080: *
081: * <p>(#) The behaviour of the JDK variant constructor changed between JDK1.3 and JDK1.4.
082: * In JDK1.3, the constructor upper cases the variant, in JDK1.4, it doesn't.
083: * Thus, the result from getVariant() may vary depending on your JDK.</p>
084: *
085: * <p>This method validates the input strictly.
086: * The language code must be lowercase.
087: * The country code must be uppercase.
088: * The separator must be an underscore.
089: * The length must be correct.
090: * </p>
091: *
092: * @param str the locale String to convert, null returns null
093: * @return a Locale, null if null input
094: * @throws IllegalArgumentException if the string is an invalid format
095: */
096: public static Locale toLocale(String str) {
097: if (str == null) {
098: return null;
099: }
100: int len = str.length();
101: if (len != 2 && len != 5 && len < 7) {
102: throw new IllegalArgumentException(
103: "Invalid locale format: " + str);
104: }
105: char ch0 = str.charAt(0);
106: char ch1 = str.charAt(1);
107: if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
108: throw new IllegalArgumentException(
109: "Invalid locale format: " + str);
110: }
111: if (len == 2) {
112: return new Locale(str, "");
113: } else {
114: if (str.charAt(2) != '_') {
115: throw new IllegalArgumentException(
116: "Invalid locale format: " + str);
117: }
118: char ch3 = str.charAt(3);
119: char ch4 = str.charAt(4);
120: if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
121: throw new IllegalArgumentException(
122: "Invalid locale format: " + str);
123: }
124: if (len == 5) {
125: return new Locale(str.substring(0, 2), str.substring(3,
126: 5));
127: } else {
128: if (str.charAt(5) != '_') {
129: throw new IllegalArgumentException(
130: "Invalid locale format: " + str);
131: }
132: return new Locale(str.substring(0, 2), str.substring(3,
133: 5), str.substring(6));
134: }
135: }
136: }
137:
138: //-----------------------------------------------------------------------
139: /**
140: * <p>Obtains the list of locales to search through when performing
141: * a locale search.</p>
142: *
143: * <pre>
144: * localeLookupList(Locale("fr","CA","xxx"))
145: * = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr")]
146: * </pre>
147: *
148: * @param locale the locale to start from
149: * @return the unmodifiable list of Locale objects, 0 being locale, never null
150: */
151: public static List localeLookupList(Locale locale) {
152: return localeLookupList(locale, locale);
153: }
154:
155: //-----------------------------------------------------------------------
156: /**
157: * <p>Obtains the list of locales to search through when performing
158: * a locale search.</p>
159: *
160: * <pre>
161: * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
162: * = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr"), Locale("en"]
163: * </pre>
164: *
165: * <p>The result list begins with the most specific locale, then the
166: * next more general and so on, finishing with the default locale.
167: * The list will never contain the same locale twice.</p>
168: *
169: * @param locale the locale to start from, null returns empty list
170: * @param defaultLocale the default locale to use if no other is found
171: * @return the unmodifiable list of Locale objects, 0 being locale, never null
172: */
173: public static List localeLookupList(Locale locale,
174: Locale defaultLocale) {
175: List list = new ArrayList(4);
176: if (locale != null) {
177: list.add(locale);
178: if (locale.getVariant().length() > 0) {
179: list.add(new Locale(locale.getLanguage(), locale
180: .getCountry()));
181: }
182: if (locale.getCountry().length() > 0) {
183: list.add(new Locale(locale.getLanguage(), ""));
184: }
185: if (list.contains(defaultLocale) == false) {
186: list.add(defaultLocale);
187: }
188: }
189: return Collections.unmodifiableList(list);
190: }
191:
192: //-----------------------------------------------------------------------
193: /**
194: * <p>Obtains an unmodifiable list of installed locales.</p>
195: *
196: * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
197: * It is more efficient, as the JDK method must create a new array each
198: * time it is called.</p>
199: *
200: * @return the unmodifiable list of available locales
201: */
202: public static List availableLocaleList() {
203: return cAvailableLocaleList;
204: }
205:
206: //-----------------------------------------------------------------------
207: /**
208: * <p>Obtains an unmodifiable set of installed locales.</p>
209: *
210: * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
211: * It is more efficient, as the JDK method must create a new array each
212: * time it is called.</p>
213: *
214: * @return the unmodifiable set of available locales
215: */
216: public static Set availableLocaleSet() {
217: Set set = cAvailableLocaleSet;
218: if (set == null) {
219: set = new HashSet(availableLocaleList());
220: set = Collections.unmodifiableSet(set);
221: cAvailableLocaleSet = set;
222: }
223: return set;
224: }
225:
226: //-----------------------------------------------------------------------
227: /**
228: * <p>Checks if the locale specified is in the list of available locales.</p>
229: *
230: * @param locale the Locale object to check if it is available
231: * @return true if the locale is a known locale
232: */
233: public static boolean isAvailableLocale(Locale locale) {
234: return availableLocaleList().contains(locale);
235: }
236:
237: //-----------------------------------------------------------------------
238: /**
239: * <p>Obtains the list of languages supported for a given country.</p>
240: *
241: * <p>This method takes a country code and searches to find the
242: * languages available for that country. Variant locales are removed.</p>
243: *
244: * @param countryCode the 2 letter country code, null returns empty
245: * @return an unmodifiable List of Locale objects, never null
246: */
247: public static List languagesByCountry(String countryCode) {
248: List langs = (List) cLanguagesByCountry.get(countryCode); //syncd
249: if (langs == null) {
250: if (countryCode != null) {
251: langs = new ArrayList();
252: List locales = availableLocaleList();
253: for (int i = 0; i < locales.size(); i++) {
254: Locale locale = (Locale) locales.get(i);
255: if (countryCode.equals(locale.getCountry())
256: && locale.getVariant().length() == 0) {
257: langs.add(locale);
258: }
259: }
260: langs = Collections.unmodifiableList(langs);
261: } else {
262: langs = Collections.EMPTY_LIST;
263: }
264: cLanguagesByCountry.put(countryCode, langs); //syncd
265: }
266: return langs;
267: }
268:
269: //-----------------------------------------------------------------------
270: /**
271: * <p>Obtains the list of countries supported for a given language.</p>
272: *
273: * <p>This method takes a language code and searches to find the
274: * countries available for that language. Variant locales are removed.</p>
275: *
276: * @param languageCode the 2 letter language code, null returns empty
277: * @return an unmodifiable List of Locale objects, never null
278: */
279: public static List countriesByLanguage(String languageCode) {
280: List countries = (List) cCountriesByLanguage.get(languageCode); //syncd
281: if (countries == null) {
282: if (languageCode != null) {
283: countries = new ArrayList();
284: List locales = availableLocaleList();
285: for (int i = 0; i < locales.size(); i++) {
286: Locale locale = (Locale) locales.get(i);
287: if (languageCode.equals(locale.getLanguage())
288: && locale.getCountry().length() != 0
289: && locale.getVariant().length() == 0) {
290: countries.add(locale);
291: }
292: }
293: countries = Collections.unmodifiableList(countries);
294: } else {
295: countries = Collections.EMPTY_LIST;
296: }
297: cCountriesByLanguage.put(languageCode, countries); //syncd
298: }
299: return countries;
300: }
301:
302: }
|