blob: 5a5768dfde48b0adc831290c09c466719336361d [file] [log] [blame]
/*
* 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;
}
}