7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package jdk.tools.jlink.internal.plugins;
26
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.IllformedLocaleException;
30 import java.util.Locale;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.function.Predicate;
36 import java.util.regex.Pattern;
37 import java.util.stream.Collectors;
38 import java.util.stream.IntStream;
39 import java.util.stream.Stream;
40 import jdk.internal.org.objectweb.asm.ClassReader;
41 import jdk.tools.jlink.internal.ResourcePrevisitor;
42 import jdk.tools.jlink.internal.StringTable;
43 import jdk.tools.jlink.plugin.LinkModule;
44 import jdk.tools.jlink.plugin.ModuleEntry;
45 import jdk.tools.jlink.plugin.PluginException;
46 import jdk.tools.jlink.plugin.ModulePool;
47 import jdk.tools.jlink.plugin.Plugin;
48
49 /**
50 * Plugin to explicitly specify the locale data included in jdk.localedata
51 * module. This plugin provides a jlink command line option "--include-locales"
52 * with an argument. The argument is a list of BCP 47 language tags separated
53 * by a comma. E.g.,
54 *
55 * "jlink --include-locales en,ja,*-IN"
56 *
57 * This option will include locale data for all available English and Japanese
58 * languages, and ones for the country of India. All other locale data are
59 * filtered out on the image creation.
60 *
61 * Here are a few assumptions:
62 *
63 * 0. All locale data in java.base are unconditionally included.
64 * 1. All the selective locale data are in jdk.localedata module
65 * 2. Their package names are constructed by appending ".ext" to
66 * the corresponding ones in java.base module.
67 * 3. Available locales string in LocaleDataMetaInfo class should
78 "sun.util.resources.cldr.ext",
79 "sun.util.resources.cldr.provider",
80 "sun.util.resources.ext",
81 "sun.util.resources.provider");
82 private static final String METAINFONAME = "LocaleDataMetaInfo";
83 private static final List<String> META_FILES = List.of(
84 ".+module-info.class",
85 ".+LocaleDataProvider.class",
86 ".+" + METAINFONAME + ".class");
87 private static final List<String> INCLUDE_LOCALE_FILES = List.of(
88 ".+sun/text/resources/ext/[^_]+_",
89 ".+sun/util/resources/ext/[^_]+_",
90 ".+sun/text/resources/cldr/ext/[^_]+_",
91 ".+sun/util/resources/cldr/ext/[^_]+_");
92 private Predicate<String> predicate;
93 private String userParam;
94 private List<Locale.LanguageRange> priorityList;
95 private List<Locale> available;
96 private List<String> filtered;
97
98 // Special COMPAT provider locales
99 private static final String jaJPJPTag = "ja-JP-JP";
100 private static final String noNONYTag = "no-NO-NY";
101 private static final String thTHTHTag = "th-TH-TH";
102 private static final Locale jaJPJP = new Locale("ja", "JP", "JP");
103 private static final Locale noNONY = new Locale("no", "NO", "NY");
104 private static final Locale thTHTH = new Locale("th", "TH", "TH");
105
106 @Override
107 public String getName() {
108 return NAME;
109 }
110
111 @Override
112 public void visit(ModulePool in, ModulePool out) {
113 in.transformAndCopy((resource) -> {
114 if (resource.getModule().equals(MODULENAME)) {
115 String path = resource.getPath();
116 resource = predicate.test(path) ? resource: null;
117 if (resource != null &&
135 }
136
137 @Override
138 public String getDescription() {
139 return PluginsResourceBundle.getDescription(NAME);
140 }
141
142 @Override
143 public boolean hasArguments() {
144 return true;
145 }
146
147 @Override
148 public String getArgumentsDescription() {
149 return PluginsResourceBundle.getArgument(NAME);
150 }
151
152 @Override
153 public void configure(Map<String, String> config) {
154 userParam = config.get(NAME);
155 priorityList = Arrays.stream(userParam.split(","))
156 .map(s -> {
157 try {
158 return new Locale.LanguageRange(s);
159 } catch (IllegalArgumentException iae) {
160 throw new IllegalArgumentException(String.format(
161 PluginsResourceBundle.getMessage(NAME + ".invalidtag"), s));
162 }
163 })
164 .collect(Collectors.toList());
165 }
166
167 @Override
168 public void previsit(ModulePool resources, StringTable strings) {
169 final Pattern p = Pattern.compile(".*((Data_)|(Names_))(?<tag>.*)\\.class");
170 Optional<LinkModule> optMod = resources.findModule(MODULENAME);
171
172 // jdk.localedata module validation
173 if (optMod.isPresent()) {
174 LinkModule module = optMod.get();
175 Set<String> packages = module.getAllPackages();
176 if (!packages.containsAll(LOCALEDATA_PACKAGES)) {
177 throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".missingpackages") +
178 LOCALEDATA_PACKAGES.stream()
179 .filter(pn -> !packages.contains(pn))
180 .collect(Collectors.joining(",\n\t")));
181 }
182
183 available = Stream.concat(module.entries()
184 .map(md -> p.matcher(md.getPath()))
185 .filter(m -> m.matches())
186 .map(m -> m.group("tag").replaceAll("_", "-")),
187 Stream.concat(Stream.of(jaJPJPTag), Stream.of(thTHTHTag)))
188 .distinct()
189 .sorted()
190 .map(IncludeLocalesPlugin::tagToLocale)
191 .collect(Collectors.toList());
192 } else {
193 // jdk.localedata is not added.
194 throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".localedatanotfound"));
195 }
196 filtered = filterLocales(available);
197
198 if (filtered.isEmpty()) {
199 throw new PluginException(
200 String.format(PluginsResourceBundle.getMessage(NAME + ".nomatchinglocales"), userParam));
201 }
202
203 List<String> value = Stream.concat(
204 META_FILES.stream(),
205 filtered.stream().flatMap(s -> includeLocaleFilePatterns(s).stream()))
206 .map(s -> "regex:" + s)
207 .collect(Collectors.toList());
208 predicate = ResourceFilter.includeFilter(value);
209 }
210
211 private List<String> includeLocaleFilePatterns(String tag) {
212 List<String> files = new ArrayList<>();
213 String pTag = tag.replaceAll("-", "_");
214 int lastDelimiter = tag.length();
215 String isoSpecial = pTag.matches("^(he|yi|id).*") ?
216 pTag.replaceFirst("he", "iw")
217 .replaceFirst("yi", "ji")
218 .replaceFirst("id", "in") : "";
219
220 // Add tag patterns including parents
221 while (true) {
222 pTag = pTag.substring(0, lastDelimiter);
223 files.addAll(includeLocaleFiles(pTag));
224
225 if (!isoSpecial.isEmpty()) {
226 isoSpecial = isoSpecial.substring(0, lastDelimiter);
227 files.addAll(includeLocaleFiles(isoSpecial));
228 }
229
230 lastDelimiter = pTag.lastIndexOf('_');
231 if (lastDelimiter == -1) {
232 break;
233 }
234 }
235
236 final String lang = pTag;
237
238 // Add possible special locales of the COMPAT provider
239 Set.of(jaJPJPTag, noNONYTag, thTHTHTag).stream()
240 .filter(stag -> lang.equals(stag.substring(0,2)))
241 .map(t -> includeLocaleFiles(t.replaceAll("-", "_")))
242 .forEach(files::addAll);
243
244 // Add possible UN.M49 files (unconditional for now) for each language
245 files.addAll(includeLocaleFiles(lang + "_[0-9]{3}"));
246 if (!isoSpecial.isEmpty()) {
247 files.addAll(includeLocaleFiles(isoSpecial + "_[0-9]{3}"));
248 }
249
250 // Add Thai BreakIterator related data files
251 if (lang.equals("th")) {
252 files.add(".+sun/text/resources/thai_dict");
253 files.add(".+sun/text/resources/[^_]+BreakIteratorData_th");
254 }
255
256 // Add Taiwan resource bundles for Hong Kong
257 if (tag.startsWith("zh-HK")) {
258 files.addAll(includeLocaleFiles("zh_TW"));
259 }
260
261 return files;
262 }
263
264 private List<String> includeLocaleFiles(String localeStr) {
265 return INCLUDE_LOCALE_FILES.stream()
266 .map(s -> s + localeStr + ".class")
267 .collect(Collectors.toList());
268 }
269
270 private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) {
271 char[] buf = new char[cr.getMaxStringLength()];
272 boolean[] modified = new boolean[1];
273
274 IntStream.range(1, cr.getItemCount())
275 .map(item -> cr.getItem(item))
276 .forEach(itemIndex -> {
277 if (bytes[itemIndex - 1] == 1 && // UTF-8
289
290 return modified[0];
291 }
292
293 private boolean filterOutUnsupportedTags(byte[] b) {
294 List<Locale> locales;
295
296 try {
297 locales = Arrays.asList(new String(b).split(" ")).stream()
298 .filter(tag -> !tag.isEmpty())
299 .map(IncludeLocalesPlugin::tagToLocale)
300 .collect(Collectors.toList());
301 } catch (IllformedLocaleException ile) {
302 // Seems not an available locales string literal.
303 return false;
304 }
305
306 byte[] filteredBytes = filterLocales(locales).stream()
307 .collect(Collectors.joining(" "))
308 .getBytes();
309 System.arraycopy(filteredBytes, 0, b, 0, filteredBytes.length);
310 Arrays.fill(b, filteredBytes.length, b.length, (byte)' ');
311 return true;
312 }
313
314 private List<String> filterLocales(List<Locale> locales) {
315 List<String> ret =
316 Locale.filter(priorityList, locales, Locale.FilteringMode.EXTENDED_FILTERING).stream()
317 .map(loc ->
318 // Locale.filter() does not preserve the case, which is
319 // significant for "variant" equality. Retrieve the original
320 // locales from the pre-filtered list.
321 locales.stream()
322 .filter(l -> l.toString().equalsIgnoreCase(loc.toString()))
323 .findAny()
324 .orElse(Locale.ROOT)
325 .toLanguageTag())
326 .collect(Collectors.toList());
327
328 // no-NO-NY.toLanguageTag() returns "nn-NO", so specially handle it here
329 if (ret.contains("no-NO")) {
330 ret.add(noNONYTag);
331 }
332
333 return ret;
334 }
335
336 private static final Locale.Builder LOCALE_BUILDER = new Locale.Builder();
337 private static Locale tagToLocale(String tag) {
338 // ISO3166 compatibility
339 tag = tag.replaceFirst("^iw", "he").replaceFirst("^ji", "yi").replaceFirst("^in", "id");
340
341 switch (tag) {
342 case jaJPJPTag:
343 return jaJPJP;
344 case noNONYTag:
345 return noNONY;
346 case thTHTHTag:
347 return thTHTH;
348 default:
349 LOCALE_BUILDER.clear();
350 LOCALE_BUILDER.setLanguageTag(tag);
351 return LOCALE_BUILDER.build();
352 }
353 }
354 }
|
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package jdk.tools.jlink.internal.plugins;
26
27 import java.util.AbstractMap;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.IllformedLocaleException;
31 import java.util.Locale;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Optional;
35 import static java.util.ResourceBundle.Control;
36 import java.util.Set;
37 import java.util.function.Predicate;
38 import java.util.regex.Pattern;
39 import java.util.stream.Collectors;
40 import java.util.stream.IntStream;
41 import java.util.stream.Stream;
42 import jdk.internal.org.objectweb.asm.ClassReader;
43 import jdk.tools.jlink.internal.ResourcePrevisitor;
44 import jdk.tools.jlink.internal.StringTable;
45 import jdk.tools.jlink.plugin.LinkModule;
46 import jdk.tools.jlink.plugin.ModuleEntry;
47 import jdk.tools.jlink.plugin.PluginException;
48 import jdk.tools.jlink.plugin.ModulePool;
49 import jdk.tools.jlink.plugin.Plugin;
50 import sun.util.cldr.CLDRBaseLocaleDataMetaInfo;
51 import sun.util.locale.provider.LocaleProviderAdapter;
52 import sun.util.locale.provider.LocaleProviderAdapter.Type;
53 import sun.util.locale.provider.ResourceBundleBasedAdapter;
54
55 /**
56 * Plugin to explicitly specify the locale data included in jdk.localedata
57 * module. This plugin provides a jlink command line option "--include-locales"
58 * with an argument. The argument is a list of BCP 47 language tags separated
59 * by a comma. E.g.,
60 *
61 * "jlink --include-locales en,ja,*-IN"
62 *
63 * This option will include locale data for all available English and Japanese
64 * languages, and ones for the country of India. All other locale data are
65 * filtered out on the image creation.
66 *
67 * Here are a few assumptions:
68 *
69 * 0. All locale data in java.base are unconditionally included.
70 * 1. All the selective locale data are in jdk.localedata module
71 * 2. Their package names are constructed by appending ".ext" to
72 * the corresponding ones in java.base module.
73 * 3. Available locales string in LocaleDataMetaInfo class should
84 "sun.util.resources.cldr.ext",
85 "sun.util.resources.cldr.provider",
86 "sun.util.resources.ext",
87 "sun.util.resources.provider");
88 private static final String METAINFONAME = "LocaleDataMetaInfo";
89 private static final List<String> META_FILES = List.of(
90 ".+module-info.class",
91 ".+LocaleDataProvider.class",
92 ".+" + METAINFONAME + ".class");
93 private static final List<String> INCLUDE_LOCALE_FILES = List.of(
94 ".+sun/text/resources/ext/[^_]+_",
95 ".+sun/util/resources/ext/[^_]+_",
96 ".+sun/text/resources/cldr/ext/[^_]+_",
97 ".+sun/util/resources/cldr/ext/[^_]+_");
98 private Predicate<String> predicate;
99 private String userParam;
100 private List<Locale.LanguageRange> priorityList;
101 private List<Locale> available;
102 private List<String> filtered;
103
104 private static final ResourceBundleBasedAdapter CLDR_ADAPTER =
105 (ResourceBundleBasedAdapter)LocaleProviderAdapter.forType(Type.CLDR);
106 private static final Map<Locale, String[]> CLDR_PARENT_LOCALES =
107 new CLDRBaseLocaleDataMetaInfo().parentLocales();
108
109 // Equivalent map
110 private static final Map<String, List<String>> EQUIV_MAP =
111 Stream.concat(
112 // COMPAT equivalence
113 Map.of(
114 "zh-Hans", List.of("zh-Hans", "zh-CN", "zh-SG"),
115 "zh-Hant", List.of("zh-Hant", "zh-HK", "zh-MO", "zh-TW"))
116 .entrySet()
117 .stream(),
118
119 // CLDR parent locales
120 CLDR_PARENT_LOCALES.entrySet().stream()
121 .map(entry -> {
122 String parent = entry.getKey().toLanguageTag();
123 List<String> children = new ArrayList<>();
124 children.add(parent);
125
126 Arrays.stream(entry.getValue())
127 .filter(child -> !child.isEmpty())
128 .flatMap(child ->
129 Stream.concat(
130 Arrays.stream(CLDR_PARENT_LOCALES.getOrDefault(
131 Locale.forLanguageTag(child), new String[0]))
132 .filter(grandchild -> !grandchild.isEmpty()),
133 List.of(child).stream()))
134 .distinct()
135 .forEach(children::add);
136 return new AbstractMap.SimpleEntry<String, List<String>>(parent, children);
137 })
138 ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
139
140 // Special COMPAT provider locales
141 private static final String jaJPJPTag = "ja-JP-JP";
142 private static final String noNONYTag = "no-NO-NY";
143 private static final String thTHTHTag = "th-TH-TH";
144 private static final Locale jaJPJP = new Locale("ja", "JP", "JP");
145 private static final Locale noNONY = new Locale("no", "NO", "NY");
146 private static final Locale thTHTH = new Locale("th", "TH", "TH");
147
148 @Override
149 public String getName() {
150 return NAME;
151 }
152
153 @Override
154 public void visit(ModulePool in, ModulePool out) {
155 in.transformAndCopy((resource) -> {
156 if (resource.getModule().equals(MODULENAME)) {
157 String path = resource.getPath();
158 resource = predicate.test(path) ? resource: null;
159 if (resource != null &&
177 }
178
179 @Override
180 public String getDescription() {
181 return PluginsResourceBundle.getDescription(NAME);
182 }
183
184 @Override
185 public boolean hasArguments() {
186 return true;
187 }
188
189 @Override
190 public String getArgumentsDescription() {
191 return PluginsResourceBundle.getArgument(NAME);
192 }
193
194 @Override
195 public void configure(Map<String, String> config) {
196 userParam = config.get(NAME);
197
198 try {
199 priorityList = Locale.LanguageRange.parse(userParam, EQUIV_MAP);
200 } catch (IllegalArgumentException iae) {
201 throw new IllegalArgumentException(String.format(
202 PluginsResourceBundle.getMessage(NAME + ".invalidtag"),
203 iae.getMessage().replaceFirst("^range=", "")));
204 }
205 }
206
207 @Override
208 public void previsit(ModulePool resources, StringTable strings) {
209 final Pattern p = Pattern.compile(".*((Data_)|(Names_))(?<tag>.*)\\.class");
210 Optional<LinkModule> optMod = resources.findModule(MODULENAME);
211
212 // jdk.localedata module validation
213 if (optMod.isPresent()) {
214 LinkModule module = optMod.get();
215 Set<String> packages = module.getAllPackages();
216 if (!packages.containsAll(LOCALEDATA_PACKAGES)) {
217 throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".missingpackages") +
218 LOCALEDATA_PACKAGES.stream()
219 .filter(pn -> !packages.contains(pn))
220 .collect(Collectors.joining(",\n\t")));
221 }
222
223 available = Stream.concat(module.entries()
224 .map(md -> p.matcher(md.getPath()))
225 .filter(m -> m.matches())
226 .map(m -> m.group("tag").replaceAll("_", "-")),
227 Stream.concat(Stream.of(jaJPJPTag), Stream.of(thTHTHTag)))
228 .distinct()
229 .sorted()
230 .map(IncludeLocalesPlugin::tagToLocale)
231 .collect(Collectors.toList());
232 } else {
233 // jdk.localedata is not added.
234 throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".localedatanotfound"));
235 }
236
237 filtered = filterLocales(available);
238
239 if (filtered.isEmpty()) {
240 throw new PluginException(
241 String.format(PluginsResourceBundle.getMessage(NAME + ".nomatchinglocales"), userParam));
242 }
243
244 List<String> value = Stream.concat(
245 META_FILES.stream(),
246 filtered.stream().flatMap(s -> includeLocaleFilePatterns(s).stream()))
247 .map(s -> "regex:" + s)
248 .collect(Collectors.toList());
249
250 predicate = ResourceFilter.includeFilter(value);
251 }
252
253 private List<String> includeLocaleFilePatterns(String tag) {
254 // Ignore extension variations
255 if (tag.matches(".+-[a-z]-.+")) {
256 return List.of();
257 }
258
259 List<String> files = new ArrayList<>(includeLocaleFiles(tag.replaceAll("-", "_")));
260
261 // Add Thai BreakIterator related data files
262 if (tag.equals("th")) {
263 files.add(".+sun/text/resources/thai_dict");
264 files.add(".+sun/text/resources/[^_]+BreakIteratorData_th");
265 }
266
267 // Add Taiwan resource bundles for Hong Kong
268 if (tag.equals("zh-HK")) {
269 files.addAll(includeLocaleFiles("zh_TW"));
270 }
271
272 return files;
273 }
274
275 private List<String> includeLocaleFiles(String localeStr) {
276 return INCLUDE_LOCALE_FILES.stream()
277 .map(s -> s + localeStr + ".class")
278 .collect(Collectors.toList());
279 }
280
281 private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) {
282 char[] buf = new char[cr.getMaxStringLength()];
283 boolean[] modified = new boolean[1];
284
285 IntStream.range(1, cr.getItemCount())
286 .map(item -> cr.getItem(item))
287 .forEach(itemIndex -> {
288 if (bytes[itemIndex - 1] == 1 && // UTF-8
300
301 return modified[0];
302 }
303
304 private boolean filterOutUnsupportedTags(byte[] b) {
305 List<Locale> locales;
306
307 try {
308 locales = Arrays.asList(new String(b).split(" ")).stream()
309 .filter(tag -> !tag.isEmpty())
310 .map(IncludeLocalesPlugin::tagToLocale)
311 .collect(Collectors.toList());
312 } catch (IllformedLocaleException ile) {
313 // Seems not an available locales string literal.
314 return false;
315 }
316
317 byte[] filteredBytes = filterLocales(locales).stream()
318 .collect(Collectors.joining(" "))
319 .getBytes();
320
321 if (filteredBytes.length > b.length) {
322 throw new InternalError("Size of filtered locales is bigger than the original one");
323 }
324
325 System.arraycopy(filteredBytes, 0, b, 0, filteredBytes.length);
326 Arrays.fill(b, filteredBytes.length, b.length, (byte)' ');
327 return true;
328 }
329
330 private List<String> filterLocales(List<Locale> locales) {
331 List<String> ret =
332 Locale.filter(priorityList, locales, Locale.FilteringMode.EXTENDED_FILTERING).stream()
333 .flatMap(loc -> Stream.concat(Control.getNoFallbackControl(Control.FORMAT_DEFAULT)
334 .getCandidateLocales("", loc).stream(),
335 CLDR_ADAPTER.getCandidateLocales("", loc).stream()))
336 .map(loc ->
337 // Locale.filter() does not preserve the case, which is
338 // significant for "variant" equality. Retrieve the original
339 // locales from the pre-filtered list.
340 locales.stream()
341 .filter(l -> l.toString().equalsIgnoreCase(loc.toString()))
342 .findAny()
343 .orElse(Locale.ROOT))
344 .filter(loc -> !loc.equals(Locale.ROOT))
345 .flatMap(IncludeLocalesPlugin::localeToTags)
346 .distinct()
347 .collect(Collectors.toList());
348
349 return ret;
350 }
351
352 private static final Locale.Builder LOCALE_BUILDER = new Locale.Builder();
353 private static Locale tagToLocale(String tag) {
354 // ISO3166 compatibility
355 tag = tag.replaceFirst("^iw", "he").replaceFirst("^ji", "yi").replaceFirst("^in", "id");
356
357 // Special COMPAT provider locales
358 switch (tag) {
359 case jaJPJPTag:
360 return jaJPJP;
361 case noNONYTag:
362 return noNONY;
363 case thTHTHTag:
364 return thTHTH;
365 default:
366 LOCALE_BUILDER.clear();
367 LOCALE_BUILDER.setLanguageTag(tag);
368 return LOCALE_BUILDER.build();
369 }
370 }
371
372 private static Stream<String> localeToTags(Locale loc) {
373 String tag = loc.toLanguageTag();
374 Stream<String> ret = null;
375
376 switch (loc.getLanguage()) {
377 // ISO3166 compatibility
378 case "iw":
379 ret = List.of(tag, tag.replaceFirst("^he", "iw")).stream();
380 break;
381 case "in":
382 ret = List.of(tag, tag.replaceFirst("^id", "in")).stream();
383 break;
384 case "ji":
385 ret = List.of(tag, tag.replaceFirst("^yi", "ji")).stream();
386 break;
387
388 // Special COMPAT provider locales
389 case "ja":
390 if (loc.getCountry() == "JP") {
391 ret = List.of(tag, jaJPJPTag).stream();
392 }
393 break;
394 case "no":
395 case "nn":
396 if (loc.getCountry() == "NO") {
397 ret = List.of(tag, noNONYTag).stream();
398 }
399 break;
400 case "th":
401 if (loc.getCountry() == "TH") {
402 ret = List.of(tag, thTHTHTag).stream();
403 }
404 break;
405 }
406
407 return ret == null ? List.of(tag).stream() : ret;
408 }
409 }
|