src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java
author phh
Sat, 30 Nov 2019 14:33:05 -0800
changeset 59330 5b96c12f909d
parent 47216 71c04702a3d5
permissions -rw-r--r--
8234541: C1 emits an empty message when it inlines successfully Summary: Use "inline" as the message when successfull Reviewed-by: thartmann, mdoerr Contributed-by: navy.xliu@gmail.com

/*
 * Copyright (c) 2015, 2017, 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;

import java.lang.module.ModuleDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import jdk.internal.jimage.decompressor.CompressedResourceHeader;
import jdk.internal.module.Resources;
import jdk.internal.module.ModuleInfo;
import jdk.internal.module.ModuleInfo.Attributes;
import jdk.internal.module.ModuleTarget;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
import jdk.tools.jlink.plugin.ResourcePoolModule;
import jdk.tools.jlink.plugin.ResourcePoolModuleView;
import jdk.tools.jlink.plugin.PluginException;

/**
 * A manager for pool of resources.
 */
public class ResourcePoolManager {
    // utility to read Module Attributes of the given ResourcePoolModule
    static Attributes readModuleAttributes(ResourcePoolModule mod) {
        String p = "/" + mod.name() + "/module-info.class";
        Optional<ResourcePoolEntry> content = mod.findEntry(p);
        if (!content.isPresent()) {
              throw new PluginException("module-info.class not found for " +
                  mod.name() + " module");
        }
        ByteBuffer bb = ByteBuffer.wrap(content.get().contentBytes());
        try {
            return ModuleInfo.read(bb, null);
        } catch (RuntimeException re) {
            throw new RuntimeException("module info cannot be read for " + mod.name(), re);
        }
    }

    /**
     * Returns true if a resource has an effective package.
     */
    public static boolean isNamedPackageResource(String path) {
        return (path.endsWith(".class") && !path.endsWith("module-info.class")) ||
                Resources.canEncapsulate(path);
    }

    class ResourcePoolModuleImpl implements ResourcePoolModule {

        final Map<String, ResourcePoolEntry> moduleContent = new LinkedHashMap<>();
        // lazily initialized
        private ModuleDescriptor descriptor;
        private ModuleTarget target;

        final String name;

        private ResourcePoolModuleImpl(String name) {
            this.name = name;
        }

        @Override
        public String name() {
            return name;
        }

        @Override
        public Optional<ResourcePoolEntry> findEntry(String path) {
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (!path.startsWith("/" + name + "/")) {
                path = "/" + name + path; // path already starts with '/'
            }
            return Optional.ofNullable(moduleContent.get(path));
        }

        @Override
        public ModuleDescriptor descriptor() {
            initModuleAttributes();
            return descriptor;
        }

        @Override
        public String targetPlatform() {
            initModuleAttributes();
            return target != null? target.targetPlatform() : null;
        }

        private void initModuleAttributes() {
            if (this.descriptor == null) {
                Attributes attr = readModuleAttributes(this);
                this.descriptor = attr.descriptor();
                this.target = attr.target();
            }
        }

        @Override
        public Set<String> packages() {
            Set<String> pkgs = new HashSet<>();
            moduleContent.values().stream()
                .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
                .forEach(res -> {
                    String name = ImageFileCreator.resourceName(res.path());
                    if (isNamedPackageResource(name)) {
                        String pkg = ImageFileCreator.toPackage(name);
                        if (!pkg.isEmpty()) {
                            pkgs.add(pkg);
                        }
                    }
                });
            return pkgs;
        }

        @Override
        public String toString() {
            return name();
        }

        @Override
        public Stream<ResourcePoolEntry> entries() {
            return moduleContent.values().stream();
        }

        @Override
        public int entryCount() {
            return moduleContent.values().size();
        }
    }

    public class ResourcePoolImpl implements ResourcePool {
        @Override
        public ResourcePoolModuleView moduleView() {
            return ResourcePoolManager.this.moduleView();
        }

        @Override
        public Stream<ResourcePoolEntry> entries() {
            return ResourcePoolManager.this.entries();
        }

        @Override
        public int entryCount() {
            return ResourcePoolManager.this.entryCount();
        }

        @Override
        public Optional<ResourcePoolEntry> findEntry(String path) {
            return ResourcePoolManager.this.findEntry(path);
        }

        @Override
        public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
            return ResourcePoolManager.this.findEntryInContext(path, context);
        }

        @Override
        public boolean contains(ResourcePoolEntry data) {
            return ResourcePoolManager.this.contains(data);
        }

        @Override
        public boolean isEmpty() {
            return ResourcePoolManager.this.isEmpty();
        }

        @Override
        public ByteOrder byteOrder() {
            return ResourcePoolManager.this.byteOrder();
        }

        public StringTable getStringTable() {
            return ResourcePoolManager.this.getStringTable();
        }
    }

    class ResourcePoolBuilderImpl implements ResourcePoolBuilder {
        private boolean built;

        @Override
        public void add(ResourcePoolEntry data) {
            if (built) {
                throw new IllegalStateException("resource pool already built!");
            }
            ResourcePoolManager.this.add(data);
        }

        @Override
        public ResourcePool build() {
            built = true;
            return ResourcePoolManager.this.resourcePool();
        }
    }

    class ResourcePoolModuleViewImpl implements ResourcePoolModuleView {
        @Override
        public Optional<ResourcePoolModule> findModule(String name) {
            return ResourcePoolManager.this.findModule(name);
        }

        @Override
        public Stream<ResourcePoolModule> modules() {
            return ResourcePoolManager.this.modules();
        }

        @Override
        public int moduleCount() {
            return ResourcePoolManager.this.moduleCount();
        }
    }

    private final Map<String, ResourcePoolEntry> resources = new LinkedHashMap<>();
    private final Map<String, ResourcePoolModule> modules = new LinkedHashMap<>();
    private final ByteOrder order;
    private final StringTable table;
    private final ResourcePool poolImpl;
    private final ResourcePoolBuilder poolBuilderImpl;
    private final ResourcePoolModuleView moduleViewImpl;

    public ResourcePoolManager() {
        this(ByteOrder.nativeOrder());
    }

    public ResourcePoolManager(ByteOrder order) {
        this(order, new StringTable() {

            @Override
            public int addString(String str) {
                return -1;
            }

            @Override
            public String getString(int id) {
                return null;
            }
        });
    }

    public ResourcePoolManager(ByteOrder order, StringTable table) {
        this.order = Objects.requireNonNull(order);
        this.table = Objects.requireNonNull(table);
        this.poolImpl = new ResourcePoolImpl();
        this.poolBuilderImpl = new ResourcePoolBuilderImpl();
        this.moduleViewImpl = new ResourcePoolModuleViewImpl();
    }

    public ResourcePool resourcePool() {
        return poolImpl;
    }

    public ResourcePoolBuilder resourcePoolBuilder() {
        return poolBuilderImpl;
    }

    public ResourcePoolModuleView moduleView() {
        return moduleViewImpl;
    }

    /**
     * Add a ResourcePoolEntry.
     *
     * @param data The ResourcePoolEntry to add.
     */
    public void add(ResourcePoolEntry data) {
        Objects.requireNonNull(data);
        if (resources.get(data.path()) != null) {
            throw new PluginException("Resource " + data.path()
                    + " already present");
        }
        String modulename = data.moduleName();
        ResourcePoolModuleImpl m = (ResourcePoolModuleImpl)modules.get(modulename);
        if (m == null) {
            m = new ResourcePoolModuleImpl(modulename);
            modules.put(modulename, m);
        }
        resources.put(data.path(), data);
        m.moduleContent.put(data.path(), data);
    }

    /**
     * Retrieves the module for the provided name.
     *
     * @param name The module name
     * @return the module of matching name, if found
     */
    public Optional<ResourcePoolModule> findModule(String name) {
        Objects.requireNonNull(name);
        return Optional.ofNullable(modules.get(name));
    }

    /**
     * The stream of modules contained in this ResourcePool.
     *
     * @return The stream of modules.
     */
    public Stream<ResourcePoolModule> modules() {
        return modules.values().stream();
    }

    /**
     * Return the number of ResourcePoolModule count in this ResourcePool.
     *
     * @return the module count.
     */
    public int moduleCount() {
        return modules.size();
    }

    /**
     * Get all ResourcePoolEntry contained in this ResourcePool instance.
     *
     * @return The stream of ResourcePoolModuleEntries.
     */
    public Stream<ResourcePoolEntry> entries() {
        return resources.values().stream();
    }

    /**
     * Return the number of ResourcePoolEntry count in this ResourcePool.
     *
     * @return the entry count.
     */
    public int entryCount() {
        return resources.values().size();
    }

    /**
     * Get the ResourcePoolEntry for the passed path.
     *
     * @param path A data path
     * @return A ResourcePoolEntry instance or null if the data is not found
     */
    public Optional<ResourcePoolEntry> findEntry(String path) {
        Objects.requireNonNull(path);
        return Optional.ofNullable(resources.get(path));
    }

    /**
     * Get the ResourcePoolEntry for the passed path restricted to supplied context.
     *
     * @param path A data path
     * @param context A context of the search
     * @return A ResourcePoolEntry instance or null if the data is not found
     */
    public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
        Objects.requireNonNull(path);
        Objects.requireNonNull(context);
        ResourcePoolModule module = modules.get(context.moduleName());
        Objects.requireNonNull(module);
        Optional<ResourcePoolEntry> entry = module.findEntry(path);
        // Navigating other modules via requires and exports is problematic
        // since we cannot construct the runtime model of loaders and layers.
        return entry;
     }

    /**
     * Check if the ResourcePool contains the given ResourcePoolEntry.
     *
     * @param data The module data to check existence for.
     * @return The module data or null if not found.
     */
    public boolean contains(ResourcePoolEntry data) {
        Objects.requireNonNull(data);
        return findEntry(data.path()).isPresent();
    }

    /**
     * Check if the ResourcePool contains some content at all.
     *
     * @return True, no content, false otherwise.
     */
    public boolean isEmpty() {
        return resources.isEmpty();
    }

    /**
     * The ByteOrder currently in use when generating the jimage file.
     *
     * @return The ByteOrder.
     */
    public ByteOrder byteOrder() {
        return order;
    }

    public StringTable getStringTable() {
        return table;
    }

    /**
     * A resource that has been compressed.
     */
    public static final class CompressedModuleData extends ByteArrayResourcePoolEntry {

        final long uncompressed_size;

        private CompressedModuleData(String module, String path,
                byte[] content, long uncompressed_size) {
            super(module, path, ResourcePoolEntry.Type.CLASS_OR_RESOURCE, content);
            this.uncompressed_size = uncompressed_size;
        }

        public long getUncompressedSize() {
            return uncompressed_size;
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof CompressedModuleData)) {
                return false;
            }
            CompressedModuleData f = (CompressedModuleData) other;
            return f.path().equals(path());
        }

        @Override
        public int hashCode() {
            return super.hashCode();
        }
    }

    public static CompressedModuleData newCompressedResource(ResourcePoolEntry original,
            ByteBuffer compressed,
            String plugin, String pluginConfig, StringTable strings,
            ByteOrder order) {
        Objects.requireNonNull(original);
        Objects.requireNonNull(compressed);
        Objects.requireNonNull(plugin);

        boolean isTerminal = !(original instanceof CompressedModuleData);
        long uncompressed_size = original.contentLength();
        if (original instanceof CompressedModuleData) {
            CompressedModuleData comp = (CompressedModuleData) original;
            uncompressed_size = comp.getUncompressedSize();
        }
        int nameOffset = strings.addString(plugin);
        int configOffset = -1;
        if (pluginConfig != null) {
            configOffset = strings.addString(plugin);
        }
        CompressedResourceHeader rh
                = new CompressedResourceHeader(compressed.limit(), original.contentLength(),
                        nameOffset, configOffset, isTerminal);
        // Merge header with content;
        byte[] h = rh.getBytes(order);
        ByteBuffer bb = ByteBuffer.allocate(compressed.limit() + h.length);
        bb.order(order);
        bb.put(h);
        bb.put(compressed);
        byte[] contentWithHeader = bb.array();

        CompressedModuleData compressedResource
                = new CompressedModuleData(original.moduleName(), original.path(),
                        contentWithHeader, uncompressed_size);
        return compressedResource;
    }
}