View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  import java.util.Set;
28  
29  /**
30   * <p>Operations to assist when working with a {@link Locale}.</p>
31   *
32   * <p>This class tries to handle <code>null</code> input gracefully.
33   * An exception will not be thrown for a <code>null</code> input.
34   * Each method documents its behaviour in more detail.</p>
35   *
36   * @author Stephen Colebourne
37   * @since 2.2
38   * @version $Id: LocaleUtils.java 534277 2007-05-01 23:50:01Z bayard $
39   */
40  public class LocaleUtils {
41  
42      /** Unmodifiable list of available locales. */
43      private static final List cAvailableLocaleList;
44      /** Unmodifiable set of available locales. */
45      private static Set cAvailableLocaleSet;
46      /** Unmodifiable map of language locales by country. */
47      private static final Map cLanguagesByCountry = Collections.synchronizedMap(new HashMap());
48      /** Unmodifiable map of country locales by language. */
49      private static final Map cCountriesByLanguage = Collections.synchronizedMap(new HashMap());
50      static {
51          List list = Arrays.asList(Locale.getAvailableLocales());
52          cAvailableLocaleList = Collections.unmodifiableList(list);
53      }
54  
55      /**
56       * <p><code>LocaleUtils</code> instances should NOT be constructed in standard programming.
57       * Instead, the class should be used as <code>LocaleUtils.toLocale("en_GB");</code>.</p>
58       *
59       * <p>This constructor is public to permit tools that require a JavaBean instance
60       * to operate.</p>
61       */
62      public LocaleUtils() {
63        super();
64      }
65  
66      //-----------------------------------------------------------------------
67      /**
68       * <p>Converts a String to a Locale.</p>
69       *
70       * <p>This method takes the string format of a locale and creates the
71       * locale object from it.</p>
72       *
73       * <pre>
74       *   LocaleUtils.toLocale("en")         = new Locale("en", "")
75       *   LocaleUtils.toLocale("en_GB")      = new Locale("en", "GB")
76       *   LocaleUtils.toLocale("en_GB_xxx")  = new Locale("en", "GB", "xxx")   (#)
77       * </pre>
78       *
79       * <p>(#) The behaviour of the JDK variant constructor changed between JDK1.3 and JDK1.4.
80       * In JDK1.3, the constructor upper cases the variant, in JDK1.4, it doesn't.
81       * Thus, the result from getVariant() may vary depending on your JDK.</p>
82       *
83       * <p>This method validates the input strictly.
84       * The language code must be lowercase.
85       * The country code must be uppercase.
86       * The separator must be an underscore.
87       * The length must be correct.
88       * </p>
89       *
90       * @param str  the locale String to convert, null returns null
91       * @return a Locale, null if null input
92       * @throws IllegalArgumentException if the string is an invalid format
93       */
94      public static Locale toLocale(String str) {
95          if (str == null) {
96              return null;
97          }
98          int len = str.length();
99          if (len != 2 && len != 5 && len < 7) {
100             throw new IllegalArgumentException("Invalid locale format: " + str);
101         }
102         char ch0 = str.charAt(0);
103         char ch1 = str.charAt(1);
104         if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
105             throw new IllegalArgumentException("Invalid locale format: " + str);
106         }
107         if (len == 2) {
108             return new Locale(str, "");
109         } else {
110             if (str.charAt(2) != '_') {
111                 throw new IllegalArgumentException("Invalid locale format: " + str);
112             }
113             char ch3 = str.charAt(3);
114             if (ch3 == '_') {
115                 return new Locale(str.substring(0, 2), "", str.substring(4));
116             }
117             char ch4 = str.charAt(4);
118             if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
119                 throw new IllegalArgumentException("Invalid locale format: " + str);
120             }
121             if (len == 5) {
122                 return new Locale(str.substring(0, 2), str.substring(3, 5));
123             } else {
124                 if (str.charAt(5) != '_') {
125                     throw new IllegalArgumentException("Invalid locale format: " + str);
126                 }
127                 return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
128             }
129         }
130     }
131 
132     //-----------------------------------------------------------------------
133     /**
134      * <p>Obtains the list of locales to search through when performing
135      * a locale search.</p>
136      *
137      * <pre>
138      * localeLookupList(Locale("fr","CA","xxx"))
139      *   = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr")]
140      * </pre>
141      *
142      * @param locale  the locale to start from
143      * @return the unmodifiable list of Locale objects, 0 being locale, never null
144      */
145     public static List localeLookupList(Locale locale) {
146         return localeLookupList(locale, locale);
147     }
148 
149     //-----------------------------------------------------------------------
150     /**
151      * <p>Obtains the list of locales to search through when performing
152      * a locale search.</p>
153      *
154      * <pre>
155      * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
156      *   = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr"), Locale("en"]
157      * </pre>
158      *
159      * <p>The result list begins with the most specific locale, then the
160      * next more general and so on, finishing with the default locale.
161      * The list will never contain the same locale twice.</p>
162      *
163      * @param locale  the locale to start from, null returns empty list
164      * @param defaultLocale  the default locale to use if no other is found
165      * @return the unmodifiable list of Locale objects, 0 being locale, never null
166      */
167     public static List localeLookupList(Locale locale, Locale defaultLocale) {
168         List list = new ArrayList(4);
169         if (locale != null) {
170             list.add(locale);
171             if (locale.getVariant().length() > 0) {
172                 list.add(new Locale(locale.getLanguage(), locale.getCountry()));
173             }
174             if (locale.getCountry().length() > 0) {
175                 list.add(new Locale(locale.getLanguage(), ""));
176             }
177             if (list.contains(defaultLocale) == false) {
178                 list.add(defaultLocale);
179             }
180         }
181         return Collections.unmodifiableList(list);
182     }
183 
184     //-----------------------------------------------------------------------
185     /**
186      * <p>Obtains an unmodifiable list of installed locales.</p>
187      * 
188      * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
189      * It is more efficient, as the JDK method must create a new array each
190      * time it is called.</p>
191      *
192      * @return the unmodifiable list of available locales
193      */
194     public static List availableLocaleList() {
195         return cAvailableLocaleList;
196     }
197 
198     //-----------------------------------------------------------------------
199     /**
200      * <p>Obtains an unmodifiable set of installed locales.</p>
201      * 
202      * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
203      * It is more efficient, as the JDK method must create a new array each
204      * time it is called.</p>
205      *
206      * @return the unmodifiable set of available locales
207      */
208     public static Set availableLocaleSet() {
209         Set set = cAvailableLocaleSet;
210         if (set == null) {
211             set = new HashSet(availableLocaleList());
212             set = Collections.unmodifiableSet(set);
213             cAvailableLocaleSet = set;
214         }
215         return set;
216     }
217 
218     //-----------------------------------------------------------------------
219     /**
220      * <p>Checks if the locale specified is in the list of available locales.</p>
221      *
222      * @param locale the Locale object to check if it is available
223      * @return true if the locale is a known locale
224      */
225     public static boolean isAvailableLocale(Locale locale) {
226         return availableLocaleList().contains(locale);
227     }
228 
229     //-----------------------------------------------------------------------
230     /**
231      * <p>Obtains the list of languages supported for a given country.</p>
232      *
233      * <p>This method takes a country code and searches to find the
234      * languages available for that country. Variant locales are removed.</p>
235      *
236      * @param countryCode  the 2 letter country code, null returns empty
237      * @return an unmodifiable List of Locale objects, never null
238      */
239     public static List languagesByCountry(String countryCode) {
240         List langs = (List) cLanguagesByCountry.get(countryCode);  //syncd
241         if (langs == null) {
242             if (countryCode != null) {
243                 langs = new ArrayList();
244                 List locales = availableLocaleList();
245                 for (int i = 0; i < locales.size(); i++) {
246                     Locale locale = (Locale) locales.get(i);
247                     if (countryCode.equals(locale.getCountry()) &&
248                             locale.getVariant().length() == 0) {
249                         langs.add(locale);
250                     }
251                 }
252                 langs = Collections.unmodifiableList(langs);
253             } else {
254                 langs = Collections.EMPTY_LIST;
255             }
256             cLanguagesByCountry.put(countryCode, langs);  //syncd
257         }
258         return langs;
259     }
260 
261     //-----------------------------------------------------------------------
262     /**
263      * <p>Obtains the list of countries supported for a given language.</p>
264      * 
265      * <p>This method takes a language code and searches to find the
266      * countries available for that language. Variant locales are removed.</p>
267      *
268      * @param languageCode  the 2 letter language code, null returns empty
269      * @return an unmodifiable List of Locale objects, never null
270      */
271     public static List countriesByLanguage(String languageCode) {
272         List countries = (List) cCountriesByLanguage.get(languageCode);  //syncd
273         if (countries == null) {
274             if (languageCode != null) {
275                 countries = new ArrayList();
276                 List locales = availableLocaleList();
277                 for (int i = 0; i < locales.size(); i++) {
278                     Locale locale = (Locale) locales.get(i);
279                     if (languageCode.equals(locale.getLanguage()) &&
280                             locale.getCountry().length() != 0 &&
281                             locale.getVariant().length() == 0) {
282                         countries.add(locale);
283                     }
284                 }
285                 countries = Collections.unmodifiableList(countries);
286             } else {
287                 countries = Collections.EMPTY_LIST;
288             }
289             cCountriesByLanguage.put(languageCode, countries);  //syncd
290         }
291         return countries;
292     }
293 
294 }