京东自营 + 国补 iPhone 历史最低价          国家补贴 享8折

JDK14/Java14源码在线阅读

JDK14/Java14源码在线阅读 / jdk.jlink / share / classes / jdk / tools / jlink / internal / plugins / IncludeLocalesPlugin.java
/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.tools.jlink.internal.plugins;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IllformedLocaleException;
import java.util.Locale;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static java.util.ResourceBundle.Control;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.tools.jlink.internal.ResourcePrevisitor;
import jdk.tools.jlink.internal.StringTable;
import jdk.tools.jlink.plugin.ResourcePoolModule;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
import jdk.tools.jlink.plugin.Plugin;
import sun.util.cldr.CLDRBaseLocaleDataMetaInfo;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleProviderAdapter.Type;
import sun.util.locale.provider.ResourceBundleBasedAdapter;

/**
 * Plugin to explicitly specify the locale data included in jdk.localedata
 * module. This plugin provides a jlink command line option "--include-locales"
 * with an argument. The argument is a list of BCP 47 language tags separated
 * by a comma. E.g.,
 *
 *  "jlink --include-locales en,ja,*-IN"
 *
 * This option will include locale data for all available English and Japanese
 * languages, and ones for the country of India. All other locale data are
 * filtered out on the image creation.
 *
 * Here are a few assumptions:
 *
 *  0. All locale data in java.base are unconditionally included.
 *  1. All the selective locale data are in jdk.localedata module
 *  2. Their package names are constructed by appending ".ext" to
 *     the corresponding ones in java.base module.
 *  3. Available locales string in LocaleDataMetaInfo class should
 *     start with at least one white space character, e.g., " ar ar-EG ..."
 *                                                           ^
 */
public final class IncludeLocalesPlugin implements Plugin, ResourcePrevisitor {

    public static final String NAME = "include-locales";
    private static final String MODULENAME = "jdk.localedata";
    private static final Set<String> LOCALEDATA_PACKAGES = Set.of(
        "sun.text.resources.cldr.ext",
        "sun.text.resources.ext",
        "sun.util.resources.cldr.ext",
        "sun.util.resources.cldr.provider",
        "sun.util.resources.ext",
        "sun.util.resources.provider");
    private static final String METAINFONAME = "LocaleDataMetaInfo";
    private static final List<String> META_FILES = List.of(
        ".+module-info.class",
        ".+LocaleDataProvider.class",
        ".+" + METAINFONAME + ".class");
    private static final List<String> INCLUDE_LOCALE_FILES = List.of(
        ".+sun/text/resources/ext/[^_]+_",
        ".+sun/util/resources/ext/[^_]+_",
        ".+sun/text/resources/cldr/ext/[^_]+_",
        ".+sun/util/resources/cldr/ext/[^_]+_");
    private Predicate<String> predicate;
    private String userParam;
    private List<Locale.LanguageRange> priorityList;
    private List<Locale> available;
    private List<String> filtered;

    private static final ResourceBundleBasedAdapter CLDR_ADAPTER =
        (ResourceBundleBasedAdapter)LocaleProviderAdapter.forType(Type.CLDR);
    private static final Map<Locale, String[]> CLDR_PARENT_LOCALES =
        new CLDRBaseLocaleDataMetaInfo().parentLocales();

    // Equivalent map
    private static final Map<String, List<String>> EQUIV_MAP =
        Stream.concat(
            // COMPAT equivalence
            Map.of(
                "zh-Hans", List.of("zh-Hans", "zh-CN", "zh-SG"),
                "zh-Hant", List.of("zh-Hant", "zh-HK", "zh-MO", "zh-TW"))
                .entrySet()
                .stream(),

            // CLDR parent locales
            CLDR_PARENT_LOCALES.entrySet().stream()
                .map(entry -> {
                    String parent = entry.getKey().toLanguageTag();
                    List<String> children = new ArrayList<>();
                    children.add(parent);

                    Arrays.stream(entry.getValue())
                        .filter(child -> !child.isEmpty())
                        .flatMap(child ->
                            Stream.concat(
                                Arrays.stream(CLDR_PARENT_LOCALES.getOrDefault(
                                    Locale.forLanguageTag(child), new String[0]))
                                        .filter(grandchild -> !grandchild.isEmpty()),
                                List.of(child).stream()))
                        .distinct()
                        .forEach(children::add);
                    return new AbstractMap.SimpleEntry<String, List<String>>(parent, children);
                })
        ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

    // Special COMPAT provider locales
    private static final String jaJPJPTag = "ja-JP-JP";
    private static final String noNONYTag = "no-NO-NY";
    private static final String thTHTHTag = "th-TH-TH";
    private static final Locale jaJPJP = new Locale("ja", "JP", "JP");
    private static final Locale noNONY = new Locale("no", "NO", "NY");
    private static final Locale thTHTH = new Locale("th", "TH", "TH");

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
        in.transformAndCopy((resource) -> {
            if (resource.moduleName().equals(MODULENAME)) {
                String path = resource.path();
                resource = predicate.test(path) ? resource: null;
                if (resource != null &&
                    resource.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
                    byte[] bytes = resource.contentBytes();
                    ClassReader cr = new ClassReader(bytes);
                    if (Arrays.stream(cr.getInterfaces())
                        .anyMatch(i -> i.contains(METAINFONAME)) &&
                        stripUnsupportedLocales(bytes, cr)) {
                        resource = resource.copyWithContent(bytes);
                    }
                }
            }
            return resource;
        }, out);

        return out.build();
    }

    @Override
    public Category getType() {
        return Category.FILTER;
    }

    @Override
    public String getDescription() {
        return PluginsResourceBundle.getDescription(NAME);
    }

    @Override
    public boolean hasArguments() {
        return true;
    }

    @Override
    public String getArgumentsDescription() {
       return PluginsResourceBundle.getArgument(NAME);
    }

    @Override
    public void configure(Map<String, String> config) {
        userParam = config.get(NAME);

        try {
            priorityList = Locale.LanguageRange.parse(userParam, EQUIV_MAP);
        } catch (IllegalArgumentException iae) {
            throw new IllegalArgumentException(String.format(
                PluginsResourceBundle.getMessage(NAME + ".invalidtag"),
                    iae.getMessage().replaceFirst("^range=", "")));
        }
    }

    @Override
    public void previsit(ResourcePool resources, StringTable strings) {
        final Pattern p = Pattern.compile(".*((Data_)|(Names_))(?<tag>.*)\\.class");
        Optional<ResourcePoolModule> optMod = resources.moduleView().findModule(MODULENAME);

        // jdk.localedata module validation
        if (optMod.isPresent()) {
            ResourcePoolModule module = optMod.get();
            Set<String> packages = module.packages();
            if (!packages.containsAll(LOCALEDATA_PACKAGES)) {
                throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".missingpackages") +
                    LOCALEDATA_PACKAGES.stream()
                        .filter(pn -> !packages.contains(pn))
                        .collect(Collectors.joining(",\n\t")));
            }

            available = Stream.concat(module.entries()
                                        .map(md -> p.matcher(md.path()))
                                        .filter(m -> m.matches())
                                        .map(m -> m.group("tag").replaceAll("_", "-")),
                                    Stream.concat(Stream.of(jaJPJPTag), Stream.of(thTHTHTag)))
                .distinct()
                .sorted()
                .map(IncludeLocalesPlugin::tagToLocale)
                .collect(Collectors.toList());
        } else {
            // jdk.localedata is not added.
            throw new PluginException(PluginsResourceBundle.getMessage(NAME + ".localedatanotfound"));
        }

        filtered = filterLocales(available);

        if (filtered.isEmpty()) {
            throw new PluginException(
                String.format(PluginsResourceBundle.getMessage(NAME + ".nomatchinglocales"), userParam));
        }

        List<String> value = Stream.concat(
                META_FILES.stream(),
                filtered.stream().flatMap(s -> includeLocaleFilePatterns(s).stream()))
            .map(s -> "regex:" + s)
            .collect(Collectors.toList());

        predicate = ResourceFilter.includeFilter(value);
    }

    private List<String> includeLocaleFilePatterns(String tag) {
        // Ignore extension variations
        if (tag.matches(".+-[a-z]-.+")) {
            return List.of();
        }

        List<String> files = new ArrayList<>(includeLocaleFiles(tag.replaceAll("-", "_")));

        // Add Thai BreakIterator related data files
        if (tag.equals("th")) {
            files.add(".+sun/text/resources/ext/thai_dict");
            files.add(".+sun/text/resources/ext/[^_]+BreakIteratorData_th");
        }

        // Add Taiwan resource bundles for Hong Kong
        if (tag.equals("zh-HK")) {
            files.addAll(includeLocaleFiles("zh_TW"));
        }

        return files;
    }

    private List<String> includeLocaleFiles(String localeStr) {
        return INCLUDE_LOCALE_FILES.stream()
            .map(s -> s + localeStr + ".class")
            .collect(Collectors.toList());
    }

    private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) {
        char[] buf = new char[cr.getMaxStringLength()];
        boolean[] modified = new boolean[1];

        IntStream.range(1, cr.getItemCount())
            .map(item -> cr.getItem(item))
            .forEach(itemIndex -> {
                if (bytes[itemIndex - 1] == 1 &&         // UTF-8
                    bytes[itemIndex + 2] == (byte)' ') { // fast check for leading space
                    int length = cr.readUnsignedShort(itemIndex);
                    byte[] b = new byte[length];
                    System.arraycopy(bytes, itemIndex + 2, b, 0, length);
                    if (filterOutUnsupportedTags(b)) {
                        // copy back
                        System.arraycopy(b, 0, bytes, itemIndex + 2, length);
                        modified[0] = true;
                    }
                }
            });

        return modified[0];
    }

    private boolean filterOutUnsupportedTags(byte[] b) {
        List<Locale> locales;
        List<String> originalTags = Arrays.asList(new String(b).split(" "));

        try {
            locales = originalTags.stream()
                .filter(tag -> !tag.isEmpty())
                .map(IncludeLocalesPlugin::tagToLocale)
                .collect(Collectors.toList());
        } catch (IllformedLocaleException ile) {
            // Seems not an available locales string literal.
            return false;
        }

        byte[] filteredBytes = filterLocales(locales).stream()
            // Make sure the filtered language tags do exist in the
            // original supported tags for compatibility codes, e.g., "iw"
            .filter(originalTags::contains)
            .collect(Collectors.joining(" "))
            .getBytes();

        if (filteredBytes.length > b.length) {
            throw new InternalError("Size of filtered locales is bigger than the original one");
        }

        System.arraycopy(filteredBytes, 0, b, 0, filteredBytes.length);
        Arrays.fill(b, filteredBytes.length, b.length, (byte)' ');
        return true;
    }

    /*
     * Filter list of locales according to the secified priorityList. Note
     * that returned list of language tags may include extra ones, such as
     * compatibility ones (e.g., "iw" -> "iw", "he").
     */
    private List<String> filterLocales(List<Locale> locales) {
        List<String> ret =
            Locale.filter(priorityList, locales, Locale.FilteringMode.EXTENDED_FILTERING).stream()
                .flatMap(loc -> Stream.concat(Control.getNoFallbackControl(Control.FORMAT_DEFAULT)
                                     .getCandidateLocales("", loc).stream(),
                                CLDR_ADAPTER.getCandidateLocales("", loc).stream()))
                .map(loc ->
                    // Locale.filter() does not preserve the case, which is
                    // significant for "variant" equality. Retrieve the original
                    // locales from the pre-filtered list.
                    locales.stream()
                        .filter(l -> l.toString().equalsIgnoreCase(loc.toString()))
                        .findAny())
                .flatMap(Optional::stream)
                .flatMap(IncludeLocalesPlugin::localeToTags)
                .distinct()
                .collect(Collectors.toList());

        return ret;
    }

    private static final Locale.Builder LOCALE_BUILDER = new Locale.Builder();
    private static Locale tagToLocale(String tag) {
        // ISO3166 compatibility

/**代码未完, 请加载全部代码(NowJava.com).**/
展开阅读全文

关注时代Java

关注时代Java