/* * Copyright (c) 2015, 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 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 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 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 osName() { initModuleAttributes(); return target != null? target.osName() : null; } @Override public String osArch() { initModuleAttributes(); return target != null? target.osArch() : null; } private void initModuleAttributes() { if (this.descriptor == null) { Attributes attr = readModuleAttributes(this); this.descriptor = attr.descriptor(); this.target = attr.target(); } } @Override public Set packages() { Set 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 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 entries() { return ResourcePoolManager.this.entries(); } @Override public int entryCount() { return ResourcePoolManager.this.entryCount(); } @Override public Optional findEntry(String path) { return ResourcePoolManager.this.findEntry(path); } @Override public Optional 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 findModule(String name) { return ResourcePoolManager.this.findModule(name); } @Override public Stream modules() { return ResourcePoolManager.this.modules(); } @Override public int moduleCount() { return ResourcePoolManager.this.moduleCount(); } } private final Map resources = new LinkedHashMap<>(); private final Map 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 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 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 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 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 findEntryInContext(String path, ResourcePoolEntry context) { Objects.requireNonNull(path); Objects.requireNonNull(context); ResourcePoolModule module = modules.get(context.moduleName()); Objects.requireNonNull(module); Optional 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; } }