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.io.ByteArrayInputStream;
28 import java.util.Arrays;
29 import java.util.Collections;
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 java.util.Set;
36 import java.util.function.Predicate;
37 import java.util.regex.Pattern;
38 import java.util.stream.Collectors;
39 import java.util.stream.IntStream;
40 import java.util.stream.Stream;
41 import jdk.internal.org.objectweb.asm.ClassReader;
42 import jdk.tools.jlink.internal.ResourcePrevisitor;
43 import jdk.tools.jlink.internal.StringTable;
44 import jdk.tools.jlink.plugin.LinkModule;
45 import jdk.tools.jlink.plugin.ModuleEntry;
46 import jdk.tools.jlink.plugin.PluginException;
47 import jdk.tools.jlink.plugin.ModulePool;
64 * 0. All locale data in java.base are unconditionally included.
65 * 1. All the selective locale data are in jdk.localedata module
66 * 2. Their package names are constructed by appending ".ext" to
67 * the corresponding ones in java.base module.
68 * 3. Available locales string in LocaleDataMetaInfo class should
69 * start with at least one white space character, e.g., " ar ar-EG ..."
70 * ^
71 */
72 public final class IncludeLocalesPlugin implements TransformerPlugin, ResourcePrevisitor {
73
74 public static final String NAME = "include-locales";
75 private static final String MODULENAME = "jdk.localedata";
76 private static final Set<String> LOCALEDATA_PACKAGES = Set.of(
77 "sun.text.resources.cldr.ext",
78 "sun.text.resources.ext",
79 "sun.util.resources.cldr.ext",
80 "sun.util.resources.cldr.provider",
81 "sun.util.resources.ext",
82 "sun.util.resources.provider");
83 private static final String METAINFONAME = "LocaleDataMetaInfo";
84 private static final String META_FILES =
85 "*module-info.class," +
86 "*LocaleDataProvider.class," +
87 "*" + METAINFONAME + ".class,";
88 private static final String INCLUDE_LOCALE_FILES =
89 "*sun/text/resources/ext/[^\\/]+_%%.class," +
90 "*sun/util/resources/ext/[^\\/]+_%%.class," +
91 "*sun/text/resources/cldr/ext/[^\\/]+_%%.class," +
92 "*sun/util/resources/cldr/ext/[^\\/]+_%%.class,";
93 private Predicate<String> predicate;
94 private String userParam;
95 private List<Locale.LanguageRange> priorityList;
96 private List<Locale> available;
97 private List<String> filtered;
98
99 // Special COMPAT provider locales
100 private static final String jaJPJPTag = "ja-JP-JP";
101 private static final String noNONYTag = "no-NO-NY";
102 private static final String thTHTHTag = "th-TH-TH";
103 private static final Locale jaJPJP = new Locale("ja", "JP", "JP");
104 private static final Locale noNONY = new Locale("no", "NO", "NY");
105 private static final Locale thTHTH = new Locale("th", "TH", "TH");
106
107 @Override
108 public String getName() {
109 return NAME;
110 }
111
112 @Override
186 available = Stream.concat(module.entries()
187 .map(md -> p.matcher(md.getPath()))
188 .filter(m -> m.matches())
189 .map(m -> m.group("tag").replaceAll("_", "-")),
190 Stream.concat(Stream.of(jaJPJPTag), Stream.of(thTHTHTag)))
191 .distinct()
192 .sorted()
193 .map(IncludeLocalesPlugin::tagToLocale)
194 .collect(Collectors.toList());
195 } else {
196 // jdk.localedata is not added.
197 throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".localedatanotfound"));
198 }
199 filtered = filterLocales(available);
200
201 if (filtered.isEmpty()) {
202 throw new PluginException(
203 String.format(PluginsResourceBundle.getMessage(NAME + ".nomatchinglocales"), userParam));
204 }
205
206 String value = META_FILES + filtered.stream()
207 .map(s -> includeLocaleFilePatterns(s))
208 .collect(Collectors.joining(","));
209 predicate = ResourceFilter.includeFilter(value);
210 }
211
212 private String includeLocaleFilePatterns(String tag) {
213 String pTag = tag.replaceAll("-", "_");
214 String files = "";
215 int lastDelimiter = tag.length();
216 String isoSpecial = pTag.matches("^(he|yi|id).*") ?
217 pTag.replaceFirst("he", "iw")
218 .replaceFirst("yi", "ji")
219 .replaceFirst("id", "in") : "";
220
221 // Add tag patterns including parents
222 while (true) {
223 pTag = pTag.substring(0, lastDelimiter);
224 files += INCLUDE_LOCALE_FILES.replaceAll("%%", pTag);
225
226 if (!isoSpecial.isEmpty()) {
227 isoSpecial = isoSpecial.substring(0, lastDelimiter);
228 files += INCLUDE_LOCALE_FILES.replaceAll("%%", isoSpecial);
229 }
230
231 lastDelimiter = pTag.lastIndexOf('_');
232 if (lastDelimiter == -1) {
233 break;
234 }
235 }
236
237 final String lang = pTag;
238
239 // Add possible special locales of the COMPAT provider
240 files += Set.of(jaJPJPTag, noNONYTag, thTHTHTag).stream()
241 .filter(stag -> lang.equals(stag.substring(0,2)))
242 .map(t -> INCLUDE_LOCALE_FILES.replaceAll("%%", t.replaceAll("-", "_")))
243 .collect(Collectors.joining(","));
244
245 // Add possible UN.M49 files (unconditional for now) for each language
246 files += INCLUDE_LOCALE_FILES.replaceAll("%%", lang + "_[0-9]{3}");
247 if (!isoSpecial.isEmpty()) {
248 files += INCLUDE_LOCALE_FILES.replaceAll("%%", isoSpecial + "_[0-9]{3}");
249 }
250
251 // Add Thai BreakIterator related data files
252 if (lang.equals("th")) {
253 files += "*sun/text/resources/thai_dict," +
254 "*sun/text/resources/[^\\/]+BreakIteratorData_th,";
255 }
256
257 // Add Taiwan resource bundles for Hong Kong
258 if (tag.startsWith("zh-HK")) {
259 files += INCLUDE_LOCALE_FILES.replaceAll("%%", "zh_TW");
260 }
261
262 return files;
263 }
264
265 private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) {
266 char[] buf = new char[cr.getMaxStringLength()];
267 boolean[] modified = new boolean[1];
268
269 IntStream.range(1, cr.getItemCount())
270 .map(item -> cr.getItem(item))
271 .forEach(itemIndex -> {
272 if (bytes[itemIndex - 1] == 1 && // UTF-8
273 bytes[itemIndex + 2] == (byte)' ') { // fast check for leading space
274 int length = cr.readUnsignedShort(itemIndex);
275 byte[] b = new byte[length];
276 System.arraycopy(bytes, itemIndex + 2, b, 0, length);
277 if (filterOutUnsupportedTags(b)) {
278 // copy back
279 System.arraycopy(b, 0, bytes, itemIndex + 2, length);
280 modified[0] = true;
281 }
282 }
283 });
284
|
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.io.ByteArrayInputStream;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.IllformedLocaleException;
32 import java.util.Locale;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Optional;
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;
65 * 0. All locale data in java.base are unconditionally included.
66 * 1. All the selective locale data are in jdk.localedata module
67 * 2. Their package names are constructed by appending ".ext" to
68 * the corresponding ones in java.base module.
69 * 3. Available locales string in LocaleDataMetaInfo class should
70 * start with at least one white space character, e.g., " ar ar-EG ..."
71 * ^
72 */
73 public final class IncludeLocalesPlugin implements TransformerPlugin, ResourcePrevisitor {
74
75 public static final String NAME = "include-locales";
76 private static final String MODULENAME = "jdk.localedata";
77 private static final Set<String> LOCALEDATA_PACKAGES = Set.of(
78 "sun.text.resources.cldr.ext",
79 "sun.text.resources.ext",
80 "sun.util.resources.cldr.ext",
81 "sun.util.resources.cldr.provider",
82 "sun.util.resources.ext",
83 "sun.util.resources.provider");
84 private static final String METAINFONAME = "LocaleDataMetaInfo";
85 private static final List<String> META_FILES = List.of(
86 ".+module-info.class",
87 ".+LocaleDataProvider.class",
88 ".+" + METAINFONAME + ".class");
89 private static final List<String> INCLUDE_LOCALE_FILES = List.of(
90 ".+sun/text/resources/ext/[^_]+_",
91 ".+sun/util/resources/ext/[^_]+_",
92 ".+sun/text/resources/cldr/ext/[^_]+_",
93 ".+sun/util/resources/cldr/ext/[^_]+_");
94 private Predicate<String> predicate;
95 private String userParam;
96 private List<Locale.LanguageRange> priorityList;
97 private List<Locale> available;
98 private List<String> filtered;
99
100 // Special COMPAT provider locales
101 private static final String jaJPJPTag = "ja-JP-JP";
102 private static final String noNONYTag = "no-NO-NY";
103 private static final String thTHTHTag = "th-TH-TH";
104 private static final Locale jaJPJP = new Locale("ja", "JP", "JP");
105 private static final Locale noNONY = new Locale("no", "NO", "NY");
106 private static final Locale thTHTH = new Locale("th", "TH", "TH");
107
108 @Override
109 public String getName() {
110 return NAME;
111 }
112
113 @Override
187 available = Stream.concat(module.entries()
188 .map(md -> p.matcher(md.getPath()))
189 .filter(m -> m.matches())
190 .map(m -> m.group("tag").replaceAll("_", "-")),
191 Stream.concat(Stream.of(jaJPJPTag), Stream.of(thTHTHTag)))
192 .distinct()
193 .sorted()
194 .map(IncludeLocalesPlugin::tagToLocale)
195 .collect(Collectors.toList());
196 } else {
197 // jdk.localedata is not added.
198 throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".localedatanotfound"));
199 }
200 filtered = filterLocales(available);
201
202 if (filtered.isEmpty()) {
203 throw new PluginException(
204 String.format(PluginsResourceBundle.getMessage(NAME + ".nomatchinglocales"), userParam));
205 }
206
207 List<String> value = Stream.concat(
208 META_FILES.stream(),
209 filtered.stream().flatMap(s -> includeLocaleFilePatterns(s).stream()))
210 .map(s -> "regex:" + s)
211 .collect(Collectors.toList());
212 predicate = ResourceFilter.includeFilter(value);
213 }
214
215 private List<String> includeLocaleFilePatterns(String tag) {
216 List<String> files = new ArrayList<>();
217 String pTag = tag.replaceAll("-", "_");
218 int lastDelimiter = tag.length();
219 String isoSpecial = pTag.matches("^(he|yi|id).*") ?
220 pTag.replaceFirst("he", "iw")
221 .replaceFirst("yi", "ji")
222 .replaceFirst("id", "in") : "";
223
224 // Add tag patterns including parents
225 while (true) {
226 pTag = pTag.substring(0, lastDelimiter);
227 files.addAll(includeLocaleFiles(pTag));
228
229 if (!isoSpecial.isEmpty()) {
230 isoSpecial = isoSpecial.substring(0, lastDelimiter);
231 files.addAll(includeLocaleFiles(isoSpecial));
232 }
233
234 lastDelimiter = pTag.lastIndexOf('_');
235 if (lastDelimiter == -1) {
236 break;
237 }
238 }
239
240 final String lang = pTag;
241
242 // Add possible special locales of the COMPAT provider
243 files.addAll(Set.of(jaJPJPTag, noNONYTag, thTHTHTag).stream()
244 .filter(stag -> lang.equals(stag.substring(0,2)))
245 .flatMap(t -> includeLocaleFiles(t.replaceAll("-", "_")).stream())
246 .collect(Collectors.toList()));
247
248 // Add possible UN.M49 files (unconditional for now) for each language
249 files.addAll(includeLocaleFiles(lang + "_[0-9]{3}"));
250 if (!isoSpecial.isEmpty()) {
251 files.addAll(includeLocaleFiles(isoSpecial + "_[0-9]{3}"));
252 }
253
254 // Add Thai BreakIterator related data files
255 if (lang.equals("th")) {
256 files.add(".+sun/text/resources/thai_dict");
257 files.add(".+sun/text/resources/[^_]+BreakIteratorData_th");
258 }
259
260 // Add Taiwan resource bundles for Hong Kong
261 if (tag.startsWith("zh-HK")) {
262 files.addAll(includeLocaleFiles("zh_TW"));
263 }
264
265 return files;
266 }
267
268 private List<String> includeLocaleFiles(String localeStr) {
269 return INCLUDE_LOCALE_FILES.stream()
270 .map(s -> s + localeStr + ".class")
271 .collect(Collectors.toList());
272 }
273
274 private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) {
275 char[] buf = new char[cr.getMaxStringLength()];
276 boolean[] modified = new boolean[1];
277
278 IntStream.range(1, cr.getItemCount())
279 .map(item -> cr.getItem(item))
280 .forEach(itemIndex -> {
281 if (bytes[itemIndex - 1] == 1 && // UTF-8
282 bytes[itemIndex + 2] == (byte)' ') { // fast check for leading space
283 int length = cr.readUnsignedShort(itemIndex);
284 byte[] b = new byte[length];
285 System.arraycopy(bytes, itemIndex + 2, b, 0, length);
286 if (filterOutUnsupportedTags(b)) {
287 // copy back
288 System.arraycopy(b, 0, bytes, itemIndex + 2, length);
289 modified[0] = true;
290 }
291 }
292 });
293
|