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;
public class ResourcePoolManager {
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);
}
}
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<>();
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;
}
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;
}
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);
}
public Optional<ResourcePoolModule> findModule(String name) {
Objects.requireNonNull(name);
return Optional.ofNullable(modules.get(name));
}
public Stream<ResourcePoolModule> modules() {
return modules.values().stream();
}
public int moduleCount() {
return modules.size();
}
public Stream<ResourcePoolEntry> entries() {
return resources.values().stream();
}
public int entryCount() {
return resources.values().size();
}
public Optional<ResourcePoolEntry> findEntry(String path) {
Objects.requireNonNull(path);
return Optional.ofNullable(resources.get(path));
}
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);
return entry;
}
public boolean contains(ResourcePoolEntry data) {
Objects.requireNonNull(data);
return findEntry(data.path()).isPresent();
}
public boolean isEmpty() {
return resources.isEmpty();
}
public ByteOrder byteOrder() {
return order;
}
public StringTable getStringTable() {
return table;
}
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);
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;
}
}