/*
 * 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.
/** * 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.
/** * 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.
Params:
  • data – The ResourcePoolEntry to add.
/** * 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.
Params:
  • name – The module name
Returns:the module of matching name, if found
/** * 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.
Returns:The stream of modules.
/** * 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.
Returns:the module count.
/** * 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.
Returns:The stream of ResourcePoolModuleEntries.
/** * 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.
Returns:the entry count.
/** * 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.
Params:
  • path – A data path
Returns:A ResourcePoolEntry instance or null if the data is not found
/** * 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.
Params:
  • path – A data path
  • context – A context of the search
Returns:A ResourcePoolEntry instance or null if the data is not found
/** * 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.
Params:
  • data – The module data to check existence for.
Returns:The module data or null if not found.
/** * 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.
Returns:True, no content, false otherwise.
/** * 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.
Returns:The ByteOrder.
/** * 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.
/** * 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; } }