1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Set;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ConcurrentMap;
28 import java.util.function.Predicate;
29 import java.util.stream.Collectors;
30
31
32
33
34
35
36
37
38
39
40 public class LocaleUtils {
41
42
43 static class SyncAvoid {
44
45 private static final List<Locale> AVAILABLE_LOCALE_LIST;
46
47 private static final Set<Locale> AVAILABLE_LOCALE_SET;
48
49 static {
50 final List<Locale> list = new ArrayList<>(Arrays.asList(Locale.getAvailableLocales()));
51 AVAILABLE_LOCALE_LIST = Collections.unmodifiableList(list);
52 AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<>(list));
53 }
54 }
55 private static final char UNDERSCORE = '_';
56 private static final String UNDETERMINED = "und";
57
58 private static final char DASH = '-';
59
60
61 private static final ConcurrentMap<String, List<Locale>> cLanguagesByCountry =
62 new ConcurrentHashMap<>();
63
64
65 private static final ConcurrentMap<String, List<Locale>> cCountriesByLanguage =
66 new ConcurrentHashMap<>();
67
68
69
70
71
72
73
74
75
76
77 public static List<Locale> availableLocaleList() {
78 return SyncAvoid.AVAILABLE_LOCALE_LIST;
79 }
80
81 private static List<Locale> availableLocaleList(final Predicate<Locale> predicate) {
82 return availableLocaleList().stream().filter(predicate).collect(Collectors.toList());
83 }
84
85
86
87
88
89
90
91
92
93
94 public static Set<Locale> availableLocaleSet() {
95 return SyncAvoid.AVAILABLE_LOCALE_SET;
96 }
97
98
99
100
101
102
103
104
105
106
107 public static List<Locale> countriesByLanguage(final String languageCode) {
108 if (languageCode == null) {
109 return Collections.emptyList();
110 }
111 return cCountriesByLanguage.computeIfAbsent(languageCode, lc -> Collections.unmodifiableList(
112 availableLocaleList(locale -> languageCode.equals(locale.getLanguage()) && !locale.getCountry().isEmpty() && locale.getVariant().isEmpty())));
113 }
114
115
116
117
118
119
120
121 public static boolean isAvailableLocale(final Locale locale) {
122 return availableLocaleSet().contains(locale);
123 }
124
125
126
127
128
129
130
131 private static boolean isISO3166CountryCode(final String str) {
132 return StringUtils.isAllUpperCase(str) && str.length() == 2;
133 }
134
135
136
137
138
139
140
141 private static boolean isISO639LanguageCode(final String str) {
142 return StringUtils.isAllLowerCase(str) && (str.length() == 2 || str.length() == 3);
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157 public static boolean isLanguageUndetermined(final Locale locale) {
158 return locale == null || UNDETERMINED.equals(locale.toLanguageTag());
159 }
160
161
162
163
164
165
166
167 private static boolean isNumericAreaCode(final String str) {
168 return StringUtils.isNumeric(str) && str.length() == 3;
169 }
170
171
172
173
174
175
176
177
178
179
180 public static List<Locale> languagesByCountry(final String countryCode) {
181 if (countryCode == null) {
182 return Collections.emptyList();
183 }
184 return cLanguagesByCountry.computeIfAbsent(countryCode,
185 k -> Collections.unmodifiableList(availableLocaleList(locale -> countryCode.equals(locale.getCountry()) && locale.getVariant().isEmpty())));
186 }
187
188
189
190
191
192
193
194
195
196
197
198
199
200 public static List<Locale> localeLookupList(final Locale locale) {
201 return localeLookupList(locale, locale);
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221 public static List<Locale> localeLookupList(final Locale locale, final Locale defaultLocale) {
222 final List<Locale> list = new ArrayList<>(4);
223 if (locale != null) {
224 list.add(locale);
225 if (!locale.getVariant().isEmpty()) {
226 list.add(new Locale(locale.getLanguage(), locale.getCountry()));
227 }
228 if (!locale.getCountry().isEmpty()) {
229 list.add(new Locale(locale.getLanguage(), StringUtils.EMPTY));
230 }
231 if (!list.contains(defaultLocale)) {
232 list.add(defaultLocale);
233 }
234 }
235 return Collections.unmodifiableList(list);
236 }
237
238
239
240
241
242
243
244
245 private static Locale parseLocale(final String str) {
246 if (isISO639LanguageCode(str)) {
247 return new Locale(str);
248 }
249
250 final String[] segments = str.indexOf(UNDERSCORE) != -1
251 ? str.split(String.valueOf(UNDERSCORE), -1)
252 : str.split(String.valueOf(DASH), -1);
253 final String language = segments[0];
254 if (segments.length == 2) {
255 final String country = segments[1];
256 if (isISO639LanguageCode(language) && isISO3166CountryCode(country) ||
257 isNumericAreaCode(country)) {
258 return new Locale(language, country);
259 }
260 } else if (segments.length == 3) {
261 final String country = segments[1];
262 final String variant = segments[2];
263 if (isISO639LanguageCode(language) &&
264 (country.isEmpty() || isISO3166CountryCode(country) || isNumericAreaCode(country)) &&
265 !variant.isEmpty()) {
266 return new Locale(language, country, variant);
267 }
268 }
269 throw new IllegalArgumentException("Invalid locale format: " + str);
270 }
271
272
273
274
275
276
277
278
279 public static Locale toLocale(final Locale locale) {
280 return locale != null ? locale : Locale.getDefault();
281 }
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314 public static Locale toLocale(final String str) {
315 if (str == null) {
316
317 return null;
318 }
319 if (str.isEmpty()) {
320 return new Locale(StringUtils.EMPTY, StringUtils.EMPTY);
321 }
322 if (str.contains("#")) {
323 throw new IllegalArgumentException("Invalid locale format: " + str);
324 }
325 final int len = str.length();
326 if (len < 2) {
327 throw new IllegalArgumentException("Invalid locale format: " + str);
328 }
329 final char ch0 = str.charAt(0);
330 if (ch0 == UNDERSCORE || ch0 == DASH) {
331 if (len < 3) {
332 throw new IllegalArgumentException("Invalid locale format: " + str);
333 }
334 final char ch1 = str.charAt(1);
335 final char ch2 = str.charAt(2);
336 if (!Character.isUpperCase(ch1) || !Character.isUpperCase(ch2)) {
337 throw new IllegalArgumentException("Invalid locale format: " + str);
338 }
339 if (len == 3) {
340 return new Locale(StringUtils.EMPTY, str.substring(1, 3));
341 }
342 if (len < 5) {
343 throw new IllegalArgumentException("Invalid locale format: " + str);
344 }
345 if (str.charAt(3) != ch0) {
346 throw new IllegalArgumentException("Invalid locale format: " + str);
347 }
348 return new Locale(StringUtils.EMPTY, str.substring(1, 3), str.substring(4));
349 }
350
351 return parseLocale(str);
352 }
353
354
355
356
357
358
359
360
361 public LocaleUtils() {
362 }
363
364 }