package jdk.tools.jlink.internal.plugins;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleDescriptor.Version;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
import jdk.internal.module.Checks;
import jdk.internal.module.ClassFileAttributes;
import jdk.internal.module.ClassFileConstants;
import jdk.internal.module.IllegalAccessMaps;
import jdk.internal.module.ModuleHashes;
import jdk.internal.module.ModuleInfo.Attributes;
import jdk.internal.module.ModuleInfoExtender;
import jdk.internal.module.ModuleResolution;
import jdk.internal.module.ModuleTarget;
import jdk.internal.module.SystemModules;
import jdk.internal.org.objectweb.asm.Attribute;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import jdk.tools.jlink.internal.ModuleSorter;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
public final class SystemModulesPlugin implements Plugin {
private static final String NAME = "system-modules";
private static final String DESCRIPTION =
PluginsResourceBundle.getDescription(NAME);
private boolean enabled;
private boolean retainModuleTarget;
public SystemModulesPlugin() {
this.enabled = true;
this.retainModuleTarget = false;
}
@Override
public String getName() {
return NAME;
}
@Override
public String getDescription() {
return DESCRIPTION;
}
@Override
public Set<State> getState() {
return enabled ? EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL)
: EnumSet.of(State.DISABLED);
}
@Override
public boolean hasArguments() {
return true;
}
@Override
public String getArgumentsDescription() {
return PluginsResourceBundle.getArgument(NAME);
}
@Override
public void configure(Map<String, String> config) {
String arg = config.get(NAME);
if (arg != null) {
if (arg.equals("retainModuleTarget")) {
retainModuleTarget = true;
} else {
throw new IllegalArgumentException(NAME + ": " + arg);
}
}
}
@Override
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
if (!enabled) {
throw new PluginException(NAME + " was set");
}
SystemModulesClassGenerator generator =
new SystemModulesClassGenerator(retainModuleTarget);
new ModuleSorter(in.moduleView()).sorted().forEach(module -> {
ResourcePoolEntry data = module.findEntry("module-info.class").orElseThrow(
() -> new PluginException("module-info.class not found for " +
module.name() + " module")
);
assert module.name().equals(data.moduleName());
try {
data = generator.buildModuleInfo(data, module.packages());
out.add(data);
} catch (IOException e) {
throw new PluginException(e);
}
});
ClassWriter cwriter = generator.getClassWriter();
in.entries().forEach(data -> {
if (data.path().endsWith("module-info.class"))
return;
if (generator.isOverriddenClass(data.path())) {
byte[] bytes = cwriter.toByteArray();
ResourcePoolEntry ndata = data.copyWithContent(bytes);
out.add(ndata);
} else {
out.add(data);
}
});
return out.build();
}
static class ModuleInfo {
private final ByteArrayInputStream bain;
private final Attributes attrs;
private final Set<String> packages;
private final boolean dropModuleTarget;
private final boolean addModulePackages;
private ModuleDescriptor descriptor;
ModuleInfo(byte[] bytes, Set<String> packages, boolean dropModuleTarget)
throws IOException
{
this.bain = new ByteArrayInputStream(bytes);
this.packages = packages;
this.attrs = jdk.internal.module.ModuleInfo.read(bain, null);
this.descriptor = attrs.descriptor();
if (descriptor.isAutomatic()) {
throw new InternalError("linking automatic module is not supported");
}
this.addModulePackages = packages.size() > 0 && !hasModulePackages();
ModuleTarget target = attrs.target();
if (dropModuleTarget && target != null) {
this.dropModuleTarget = (target.targetPlatform() != null);
} else {
this.dropModuleTarget = false;
}
}
String moduleName() {
return attrs.descriptor().name();
}
ModuleDescriptor descriptor() {
return descriptor;
}
Set<String> packages() {
return packages;
}
ModuleTarget target() {
return attrs.target();
}
ModuleHashes recordedHashes() {
return attrs.recordedHashes();
}
ModuleResolution moduleResolution() {
return attrs.moduleResolution();
}
void validateNames() {
Checks.requireModuleName(descriptor.name());
for (Requires req : descriptor.requires()) {
Checks.requireModuleName(req.name());
}
for (Exports e : descriptor.exports()) {
Checks.requirePackageName(e.source());
if (e.isQualified())
e.targets().forEach(Checks::requireModuleName);
}
for (Opens opens : descriptor.opens()) {
Checks.requirePackageName(opens.source());
if (opens.isQualified())
opens.targets().forEach(Checks::requireModuleName);
}
for (Provides provides : descriptor.provides()) {
Checks.requireServiceTypeName(provides.service());
provides.providers().forEach(Checks::requireServiceProviderName);
}
for (String service : descriptor.uses()) {
Checks.requireServiceTypeName(service);
}
for (String pn : descriptor.packages()) {
Checks.requirePackageName(pn);
}
for (String pn : packages) {
Checks.requirePackageName(pn);
}
}
void validatePackages() {
Set<String> nonExistPackages = new TreeSet<>();
descriptor.exports().stream()
.map(Exports::source)
.filter(pn -> !packages.contains(pn))
.forEach(nonExistPackages::add);
descriptor.opens().stream()
.map(Opens::source)
.filter(pn -> !packages.contains(pn))
.forEach(nonExistPackages::add);
if (!nonExistPackages.isEmpty()) {
throw new PluginException("Packages that are exported or open in "
+ descriptor.name() + " are not present: " + nonExistPackages);
}
}
boolean hasModulePackages() throws IOException {
Set<String> attrTypes = new HashSet<>();
ClassVisitor cv = new ClassVisitor(Opcodes.ASM5) {
@Override
public void visitAttribute(Attribute attr) {
attrTypes.add(attr.type);
}
};
Attribute[] attrs = new Attribute[] {
new ClassFileAttributes.ModulePackagesAttribute()
};
try (InputStream in = getInputStream()) {
ClassReader cr = new ClassReader(in);
cr.accept(cv, attrs, 0);
return attrTypes.contains(ClassFileConstants.MODULE_PACKAGES);
}
}
boolean shouldRewrite() {
return addModulePackages || dropModuleTarget;
}
byte[] getBytes() throws IOException {
try (InputStream in = getInputStream()) {
if (shouldRewrite()) {
ModuleInfoRewriter rewriter = new ModuleInfoRewriter(in);
if (addModulePackages) {
rewriter.addModulePackages(packages);
}
if (dropModuleTarget) {
rewriter.dropModuleTarget();
}
byte[] bytes = rewriter.getBytes();
try (ByteArrayInputStream bain = new ByteArrayInputStream(bytes)) {
this.descriptor = ModuleDescriptor.read(bain);
}
return bytes;
} else {
return in.readAllBytes();
}
}
}
InputStream getInputStream() {
bain.reset();
return bain;
}
class ModuleInfoRewriter extends ByteArrayOutputStream {
final ModuleInfoExtender extender;
ModuleInfoRewriter(InputStream in) {
this.extender = ModuleInfoExtender.newExtender(in);
}
void addModulePackages(Set<String> packages) {
if (packages.size() > 0) {
extender.packages(packages);
}
}
void dropModuleTarget() {
extender.targetPlatform("");
}
byte[] getBytes() throws IOException {
extender.write(this);
return buf;
}
}
}
static class SystemModulesClassGenerator {
private static final String CLASSNAME =
"jdk/internal/module/SystemModules";
private static final String MODULE_DESCRIPTOR_BUILDER =
"jdk/internal/module/Builder";
private static final String MODULE_DESCRIPTOR_ARRAY_SIGNATURE =
"[Ljava/lang/module/ModuleDescriptor;";
private static final String REQUIRES_MODIFIER_CLASSNAME =
"java/lang/module/ModuleDescriptor$Requires$Modifier";
private static final String EXPORTS_MODIFIER_CLASSNAME =
"java/lang/module/ModuleDescriptor$Exports$Modifier";
private static final String OPENS_MODIFIER_CLASSNAME =
"java/lang/module/ModuleDescriptor$Opens$Modifier";
private static final String MODULE_TARGET_CLASSNAME =
"jdk/internal/module/ModuleTarget";
private static final String MODULE_TARGET_ARRAY_SIGNATURE =
"[Ljdk/internal/module/ModuleTarget;";
private static final String MODULE_HASHES_ARRAY_SIGNATURE =
"[Ljdk/internal/module/ModuleHashes;";
private static final String MODULE_RESOLUTION_CLASSNAME =
"jdk/internal/module/ModuleResolution";
private static final String MODULE_RESOLUTIONS_ARRAY_SIGNATURE =
"[Ljdk/internal/module/ModuleResolution;";
private static final String MODULE_NAMES = "MODULE_NAMES";
private static final String PACKAGE_COUNT = "PACKAGES_IN_BOOT_LAYER";
private static final int MAX_LOCAL_VARS = 256;
private final int BUILDER_VAR = 0;
private final int MD_VAR = 1;
private final int MT_VAR = 1;
private final int MH_VAR = 1;
private int nextLocalVar = 2;
private final ClassWriter cw;
private boolean dropModuleTarget;
private MethodVisitor mv;
private final List<ModuleInfo> moduleInfos = new ArrayList<>();
private final DedupSetBuilder dedupSetBuilder
= new DedupSetBuilder(this::getNextLocalVar);
public SystemModulesClassGenerator(boolean retainModuleTarget) {
this.cw = new ClassWriter(ClassWriter.COMPUTE_MAXS +
ClassWriter.COMPUTE_FRAMES);
this.dropModuleTarget = !retainModuleTarget;
}
private int getNextLocalVar() {
return nextLocalVar++;
}
private void clinit(int numModules, int numPackages,
boolean hasSplitPackages) {
cw.visit(Opcodes.V1_8, ACC_PUBLIC+ACC_FINAL+ACC_SUPER, CLASSNAME,
null, "java/lang/Object", null);
cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC, MODULE_NAMES,
"[Ljava/lang/String;", null, null)
.visitEnd();
cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC, PACKAGE_COUNT,
"I", null, numPackages)
.visitEnd();
MethodVisitor clinit =
cw.visitMethod(ACC_STATIC, "<clinit>", "()V",
null, null);
clinit.visitCode();
pushInt(clinit, numModules);
clinit.visitTypeInsn(ANEWARRAY, "java/lang/String");
int index = 0;
for (ModuleInfo minfo : moduleInfos) {
clinit.visitInsn(DUP);
pushInt(clinit, index++);
clinit.visitLdcInsn(minfo.moduleName());
clinit.visitInsn(AASTORE);
}
clinit.visitFieldInsn(PUTSTATIC, CLASSNAME, MODULE_NAMES,
"[Ljava/lang/String;");
clinit.visitInsn(RETURN);
clinit.visitMaxs(0, 0);
clinit.visitEnd();
MethodVisitor split =
cw.visitMethod(ACC_PUBLIC+ACC_STATIC, "hasSplitPackages",
"()Z", null, null);
split.visitCode();
split.visitInsn(hasSplitPackages ? ICONST_1 : ICONST_0);
split.visitInsn(IRETURN);
split.visitMaxs(0, 0);
split.visitEnd();
}
public ResourcePoolEntry buildModuleInfo(ResourcePoolEntry entry,
Set<String> packages)
throws IOException
{
if (moduleInfos.isEmpty() && !entry.moduleName().equals("java.base")) {
throw new InternalError("java.base must be the first module to process");
}
ModuleInfo moduleInfo;
if (entry.moduleName().equals("java.base")) {
moduleInfo = new ModuleInfo(entry.contentBytes(), packages, false);
ModuleDescriptor md = moduleInfo.descriptor;
ModuleTarget target = moduleInfo.target();
if (dropModuleTarget && target.targetPlatform() != null) {
dropModuleTarget = true;
} else {
dropModuleTarget = false;
}
} else {
moduleInfo = new ModuleInfo(entry.contentBytes(), packages, dropModuleTarget);
}
moduleInfo.validateNames();
moduleInfo.validatePackages();
if (moduleInfo.shouldRewrite()) {
entry = entry.copyWithContent(moduleInfo.getBytes());
}
moduleInfos.add(moduleInfo);
dedups(moduleInfo.descriptor());
return entry;
}
private void dedups(ModuleDescriptor md) {
for (Exports e : md.exports()) {
dedupSetBuilder.stringSet(e.targets());
dedupSetBuilder.exportsModifiers(e.modifiers());
}
for (Opens opens : md.opens()) {
dedupSetBuilder.stringSet(opens.targets());
dedupSetBuilder.opensModifiers(opens.modifiers());
}
for (Requires r : md.requires()) {
dedupSetBuilder.requiresModifiers(r.modifiers());
}
dedupSetBuilder.stringSet(md.uses());
}
public ClassWriter getClassWriter() {
int numModules = moduleInfos.size();
Set<String> allPackages = new HashSet<>();
int packageCount = 0;
for (ModuleInfo minfo : moduleInfos) {
allPackages.addAll(minfo.packages);
packageCount += minfo.packages.size();
}
int numPackages = allPackages.size();
boolean hasSplitPackages = (numPackages < packageCount);
clinit(numModules, numPackages, hasSplitPackages);
genDescriptorsMethod();
genTargetsMethod();
genHashesMethod();
genModuleResolutionsMethod();
genXXXPackagesToOpenMethods();
return cw;
}
private void genDescriptorsMethod() {
this.mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC,
"descriptors",
"()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE,
"()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE,
null);
mv.visitCode();
pushInt(mv, moduleInfos.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor");
mv.visitVarInsn(ASTORE, MD_VAR);
for (int index = 0; index < moduleInfos.size(); index++) {
ModuleInfo minfo = moduleInfos.get(index);
new ModuleDescriptorBuilder(minfo.descriptor(),
minfo.packages(),
index).build();
}
mv.visitVarInsn(ALOAD, MD_VAR);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void genTargetsMethod() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC+ACC_STATIC,
"targets",
"()" + MODULE_TARGET_ARRAY_SIGNATURE,
"()" + MODULE_TARGET_ARRAY_SIGNATURE,
null);
mv.visitCode();
pushInt(mv, moduleInfos.size());
mv.visitTypeInsn(ANEWARRAY, MODULE_TARGET_CLASSNAME);
mv.visitVarInsn(ASTORE, MT_VAR);
for (int index=0; index < moduleInfos.size(); index++) {
ModuleInfo minfo = moduleInfos.get(index);
if (minfo.target() != null && !minfo.dropModuleTarget) {
mv.visitVarInsn(ALOAD, MT_VAR);
pushInt(mv, index);
mv.visitTypeInsn(NEW, MODULE_TARGET_CLASSNAME);
mv.visitInsn(DUP);
mv.visitLdcInsn(minfo.target().targetPlatform());
mv.visitMethodInsn(INVOKESPECIAL, MODULE_TARGET_CLASSNAME,
"<init>", "(Ljava/lang/String;)V", false);
mv.visitInsn(AASTORE);
}
}
mv.visitVarInsn(ALOAD, MT_VAR);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void genHashesMethod() {
MethodVisitor hmv =
cw.visitMethod(ACC_PUBLIC + ACC_STATIC,
"hashes",
"()" + MODULE_HASHES_ARRAY_SIGNATURE,
"()" + MODULE_HASHES_ARRAY_SIGNATURE,
null);
hmv.visitCode();
pushInt(hmv, moduleInfos.size());
hmv.visitTypeInsn(ANEWARRAY, "jdk/internal/module/ModuleHashes");
hmv.visitVarInsn(ASTORE, MH_VAR);
for (int index = 0; index < moduleInfos.size(); index++) {
ModuleInfo minfo = moduleInfos.get(index);
if (minfo.recordedHashes() != null) {
new ModuleHashesBuilder(minfo.recordedHashes(),
index,
hmv).build();
}
}
hmv.visitVarInsn(ALOAD, MH_VAR);
hmv.visitInsn(ARETURN);
hmv.visitMaxs(0, 0);
hmv.visitEnd();
}
private void genModuleResolutionsMethod() {
MethodVisitor mresmv =
cw.visitMethod(ACC_PUBLIC+ACC_STATIC,
"moduleResolutions",
"()" + MODULE_RESOLUTIONS_ARRAY_SIGNATURE,
"()" + MODULE_RESOLUTIONS_ARRAY_SIGNATURE,
null);
mresmv.visitCode();
pushInt(mresmv, moduleInfos.size());
mresmv.visitTypeInsn(ANEWARRAY, MODULE_RESOLUTION_CLASSNAME);
mresmv.visitVarInsn(ASTORE, 0);
for (int index=0; index < moduleInfos.size(); index++) {
ModuleInfo minfo = moduleInfos.get(index);
if (minfo.moduleResolution() != null) {
mresmv.visitVarInsn(ALOAD, 0);
pushInt(mresmv, index);
mresmv.visitTypeInsn(NEW, MODULE_RESOLUTION_CLASSNAME);
mresmv.visitInsn(DUP);
mresmv.visitLdcInsn(minfo.moduleResolution().value());
mresmv.visitMethodInsn(INVOKESPECIAL,
MODULE_RESOLUTION_CLASSNAME,
"<init>",
"(I)V", false);
mresmv.visitInsn(AASTORE);
}
}
mresmv.visitVarInsn(ALOAD, 0);
mresmv.visitInsn(ARETURN);
mresmv.visitMaxs(0, 0);
mresmv.visitEnd();
}
private void genXXXPackagesToOpenMethods() {
List<ModuleDescriptor> descriptors = moduleInfos.stream()
.map(ModuleInfo::descriptor)
.collect(Collectors.toList());
ModuleFinder finder = finderOf(descriptors);
IllegalAccessMaps maps = IllegalAccessMaps.generate(finder);
generate("concealedPackagesToOpen", maps.concealedPackagesToOpen());
generate("exportedPackagesToOpen", maps.exportedPackagesToOpen());
}
private void generate(String methodName, Map<String, Set<String>> map) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC+ACC_STATIC,
methodName,
"()Ljava/util/Map;",
"()Ljava/util/Map;",
null);
mv.visitCode();
pushInt(mv, map.size());
mv.visitTypeInsn(ANEWARRAY, "java/util/Map$Entry");
int index = 0;
for (Map.Entry<String, Set<String>> e : map.entrySet()) {
String moduleName = e.getKey();
Set<String> packages = e.getValue();
int packageCount = packages.size();
mv.visitInsn(DUP);
pushInt(mv, index);
mv.visitLdcInsn(moduleName);
if (packageCount > 2) {
pushInt(mv, packageCount);
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
int i = 0;
for (String pn : packages) {
mv.visitInsn(DUP);
pushInt(mv, i);
mv.visitLdcInsn(pn);
mv.visitInsn(AASTORE);
i++;
}
mv.visitMethodInsn(INVOKESTATIC,
"java/util/Set",
"of",
"([Ljava/lang/Object;)Ljava/util/Set;",
true);
} else {
StringBuilder sb = new StringBuilder("(");
for (String pn : packages) {
mv.visitLdcInsn(pn);
sb.append("Ljava/lang/Object;");
}
sb.append(")Ljava/util/Set;");
mv.visitMethodInsn(INVOKESTATIC,
"java/util/Set",
"of",
sb.toString(),
true);
}
String desc = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map$Entry;";
mv.visitMethodInsn(INVOKESTATIC,
"java/util/Map",
"entry",
desc,
true);
mv.visitInsn(AASTORE);
index++;
}
mv.visitMethodInsn(INVOKESTATIC, "java/util/Map", "ofEntries",
"([Ljava/util/Map$Entry;)Ljava/util/Map;", true);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
public boolean isOverriddenClass(String path) {
return path.equals("/java.base/" + CLASSNAME + ".class");
}
void pushInt(MethodVisitor mv, int num) {
if (num <= 5) {
mv.visitInsn(ICONST_0 + num);
} else if (num < Byte.MAX_VALUE) {
mv.visitIntInsn(BIPUSH, num);
} else if (num < Short.MAX_VALUE) {
mv.visitIntInsn(SIPUSH, num);
} else {
throw new IllegalArgumentException("exceed limit: " + num);
}
}
class ModuleDescriptorBuilder {
static final String BUILDER_TYPE = "Ljdk/internal/module/Builder;";
static final String EXPORTS_TYPE =
"Ljava/lang/module/ModuleDescriptor$Exports;";
static final String OPENS_TYPE =
"Ljava/lang/module/ModuleDescriptor$Opens;";
static final String PROVIDES_TYPE =
"Ljava/lang/module/ModuleDescriptor$Provides;";
static final String REQUIRES_TYPE =
"Ljava/lang/module/ModuleDescriptor$Requires;";
static final String EXPORTS_MODIFIER_SET_STRING_SET_SIG =
"(Ljava/util/Set;Ljava/lang/String;Ljava/util/Set;)"
+ EXPORTS_TYPE;
static final String EXPORTS_MODIFIER_SET_STRING_SIG =
"(Ljava/util/Set;Ljava/lang/String;)" + EXPORTS_TYPE;
static final String OPENS_MODIFIER_SET_STRING_SET_SIG =
"(Ljava/util/Set;Ljava/lang/String;Ljava/util/Set;)"
+ OPENS_TYPE;
static final String OPENS_MODIFIER_SET_STRING_SIG =
"(Ljava/util/Set;Ljava/lang/String;)" + OPENS_TYPE;
static final String PROVIDES_STRING_LIST_SIG =
"(Ljava/lang/String;Ljava/util/List;)" + PROVIDES_TYPE;
static final String REQUIRES_SET_STRING_SIG =
"(Ljava/util/Set;Ljava/lang/String;)" + REQUIRES_TYPE;
static final String REQUIRES_SET_STRING_STRING_SIG =
"(Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;)" + REQUIRES_TYPE;
static final String EXPORTS_ARRAY_SIG =
"([" + EXPORTS_TYPE + ")" + BUILDER_TYPE;
static final String OPENS_ARRAY_SIG =
"([" + OPENS_TYPE + ")" + BUILDER_TYPE;
static final String PROVIDES_ARRAY_SIG =
"([" + PROVIDES_TYPE + ")" + BUILDER_TYPE;
static final String REQUIRES_ARRAY_SIG =
"([" + REQUIRES_TYPE + ")" + BUILDER_TYPE;
static final String SET_SIG = "(Ljava/util/Set;)" + BUILDER_TYPE;
static final String STRING_SIG = "(Ljava/lang/String;)" + BUILDER_TYPE;
static final String BOOLEAN_SIG = "(Z)" + BUILDER_TYPE;
final ModuleDescriptor md;
final Set<String> packages;
final int index;
ModuleDescriptorBuilder(ModuleDescriptor md, Set<String> packages, int index) {
if (md.isAutomatic()) {
throw new InternalError("linking automatic module is not supported");
}
this.md = md;
this.packages = packages;
this.index = index;
}
void build() {
newBuilder();
requires(md.requires());
exports(md.exports());
opens(md.opens());
uses(md.uses());
provides(md.provides());
packages(packages);
md.version().ifPresent(this::version);
md.mainClass().ifPresent(this::mainClass);
putModuleDescriptor();
}
void newBuilder() {
mv.visitTypeInsn(NEW, MODULE_DESCRIPTOR_BUILDER);
mv.visitInsn(DUP);
mv.visitLdcInsn(md.name());
mv.visitMethodInsn(INVOKESPECIAL, MODULE_DESCRIPTOR_BUILDER,
"<init>", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ASTORE, BUILDER_VAR);
mv.visitVarInsn(ALOAD, BUILDER_VAR);
if (md.isOpen()) {
setModuleBit("open", true);
}
if (md.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC)) {
setModuleBit("synthetic", true);
}
if (md.modifiers().contains(ModuleDescriptor.Modifier.MANDATED)) {
setModuleBit("mandated", true);
}
}
void setModuleBit(String methodName, boolean value) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
if (value) {
mv.visitInsn(ICONST_1);
} else {
mv.visitInsn(ICONST_0);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
methodName, BOOLEAN_SIG, false);
mv.visitInsn(POP);
}
void putModuleDescriptor() {
mv.visitVarInsn(ALOAD, MD_VAR);
pushInt(mv, index);
mv.visitVarInsn(ALOAD, BUILDER_VAR);
mv.visitLdcInsn(md.hashCode());
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"build", "(I)Ljava/lang/module/ModuleDescriptor;",
false);
mv.visitInsn(AASTORE);
}
void requires(Set<Requires> requires) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
pushInt(mv, requires.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Requires");
int arrayIndex = 0;
for (Requires require : requires) {
String compiledVersion = null;
if (require.compiledVersion().isPresent()) {
compiledVersion = require.compiledVersion().get().toString();
}
mv.visitInsn(DUP);
pushInt(mv, arrayIndex++);
newRequires(require.modifiers(), require.name(), compiledVersion);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"requires", REQUIRES_ARRAY_SIG, false);
}
void newRequires(Set<Requires.Modifier> mods, String name, String compiledVersion) {
int varIndex = dedupSetBuilder.indexOfRequiresModifiers(mods);
mv.visitVarInsn(ALOAD, varIndex);
mv.visitLdcInsn(name);
if (compiledVersion != null) {
mv.visitLdcInsn(compiledVersion);
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newRequires", REQUIRES_SET_STRING_STRING_SIG, false);
} else {
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newRequires", REQUIRES_SET_STRING_SIG, false);
}
}
void exports(Set<Exports> exports) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
pushInt(mv, exports.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Exports");
int arrayIndex = 0;
for (Exports export : exports) {
mv.visitInsn(DUP);
pushInt(mv, arrayIndex++);
newExports(export.modifiers(), export.source(), export.targets());
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"exports", EXPORTS_ARRAY_SIG, false);
}
void newExports(Set<Exports.Modifier> ms, String pn, Set<String> targets) {
int modifiersSetIndex = dedupSetBuilder.indexOfExportsModifiers(ms);
if (!targets.isEmpty()) {
int stringSetIndex = dedupSetBuilder.indexOfStringSet(targets);
mv.visitVarInsn(ALOAD, modifiersSetIndex);
mv.visitLdcInsn(pn);
mv.visitVarInsn(ALOAD, stringSetIndex);
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newExports", EXPORTS_MODIFIER_SET_STRING_SET_SIG, false);
} else {
mv.visitVarInsn(ALOAD, modifiersSetIndex);
mv.visitLdcInsn(pn);
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newExports", EXPORTS_MODIFIER_SET_STRING_SIG, false);
}
}
void opens(Set<Opens> opens) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
pushInt(mv, opens.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Opens");
int arrayIndex = 0;
for (Opens open : opens) {
mv.visitInsn(DUP);
pushInt(mv, arrayIndex++);
newOpens(open.modifiers(), open.source(), open.targets());
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"opens", OPENS_ARRAY_SIG, false);
}
void newOpens(Set<Opens.Modifier> ms, String pn, Set<String> targets) {
int modifiersSetIndex = dedupSetBuilder.indexOfOpensModifiers(ms);
if (!targets.isEmpty()) {
int stringSetIndex = dedupSetBuilder.indexOfStringSet(targets);
mv.visitVarInsn(ALOAD, modifiersSetIndex);
mv.visitLdcInsn(pn);
mv.visitVarInsn(ALOAD, stringSetIndex);
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newOpens", OPENS_MODIFIER_SET_STRING_SET_SIG, false);
} else {
mv.visitVarInsn(ALOAD, modifiersSetIndex);
mv.visitLdcInsn(pn);
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newOpens", OPENS_MODIFIER_SET_STRING_SIG, false);
}
}
void uses(Set<String> uses) {
int varIndex = dedupSetBuilder.indexOfStringSet(uses);
mv.visitVarInsn(ALOAD, BUILDER_VAR);
mv.visitVarInsn(ALOAD, varIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"uses", SET_SIG, false);
mv.visitInsn(POP);
}
void provides(Collection<Provides> provides) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
pushInt(mv, provides.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Provides");
int arrayIndex = 0;
for (Provides provide : provides) {
mv.visitInsn(DUP);
pushInt(mv, arrayIndex++);
newProvides(provide.service(), provide.providers());
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"provides", PROVIDES_ARRAY_SIG, false);
}
void newProvides(String service, List<String> providers) {
mv.visitLdcInsn(service);
pushInt(mv, providers.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
int arrayIndex = 0;
for (String provider : providers) {
mv.visitInsn(DUP);
pushInt(mv, arrayIndex++);
mv.visitLdcInsn(provider);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKESTATIC, "java/util/List",
"of", "([Ljava/lang/Object;)Ljava/util/List;", true);
mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
"newProvides", PROVIDES_STRING_LIST_SIG, false);
}
void packages(Set<String> packages) {
int varIndex = dedupSetBuilder.newStringSet(packages);
mv.visitVarInsn(ALOAD, BUILDER_VAR);
mv.visitVarInsn(ALOAD, varIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"packages", SET_SIG, false);
mv.visitInsn(POP);
}
void mainClass(String cn) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
mv.visitLdcInsn(cn);
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"mainClass", STRING_SIG, false);
mv.visitInsn(POP);
}
void version(Version v) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
mv.visitLdcInsn(v.toString());
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"version", STRING_SIG, false);
mv.visitInsn(POP);
}
void invokeBuilderMethod(String methodName, String value) {
mv.visitVarInsn(ALOAD, BUILDER_VAR);
mv.visitLdcInsn(value);
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
methodName, STRING_SIG, false);
mv.visitInsn(POP);
}
}
class ModuleHashesBuilder {
private static final String MODULE_HASHES_BUILDER =
"jdk/internal/module/ModuleHashes$Builder";
private static final String MODULE_HASHES_BUILDER_TYPE =
"L" + MODULE_HASHES_BUILDER + ";";
static final String STRING_BYTE_ARRAY_SIG =
"(Ljava/lang/String;[B)" + MODULE_HASHES_BUILDER_TYPE;
final ModuleHashes recordedHashes;
final MethodVisitor hmv;
final int index;
ModuleHashesBuilder(ModuleHashes hashes, int index, MethodVisitor hmv) {
this.recordedHashes = hashes;
this.hmv = hmv;
this.index = index;
}
void build() {
if (recordedHashes == null)
return;
newModuleHashesBuilder();
recordedHashes
.names()
.forEach(mn -> hashForModule(mn, recordedHashes.hashFor(mn)));
pushModuleHashes();
}
void newModuleHashesBuilder() {
hmv.visitTypeInsn(NEW, MODULE_HASHES_BUILDER);
hmv.visitInsn(DUP);
hmv.visitLdcInsn(recordedHashes.algorithm());
pushInt(hmv, ((4 * recordedHashes.names().size()) / 3) + 1);
hmv.visitMethodInsn(INVOKESPECIAL, MODULE_HASHES_BUILDER,
"<init>", "(Ljava/lang/String;I)V", false);
hmv.visitVarInsn(ASTORE, BUILDER_VAR);
hmv.visitVarInsn(ALOAD, BUILDER_VAR);
}
void pushModuleHashes() {
hmv.visitVarInsn(ALOAD, MH_VAR);
pushInt(hmv, index);
hmv.visitVarInsn(ALOAD, BUILDER_VAR);
hmv.visitMethodInsn(INVOKEVIRTUAL, MODULE_HASHES_BUILDER,
"build", "()Ljdk/internal/module/ModuleHashes;",
false);
hmv.visitInsn(AASTORE);
}
void hashForModule(String name, byte[] hash) {
hmv.visitVarInsn(ALOAD, BUILDER_VAR);
hmv.visitLdcInsn(name);
pushInt(hmv, hash.length);
hmv.visitIntInsn(NEWARRAY, T_BYTE);
for (int i = 0; i < hash.length; i++) {
hmv.visitInsn(DUP);
pushInt(hmv, i);
hmv.visitIntInsn(BIPUSH, hash[i]);
hmv.visitInsn(BASTORE);
}
hmv.visitMethodInsn(INVOKEVIRTUAL, MODULE_HASHES_BUILDER,
"hashForModule", STRING_BYTE_ARRAY_SIG, false);
hmv.visitInsn(POP);
}
}
class DedupSetBuilder {
final Map<Set<String>, SetBuilder<String>> stringSets = new HashMap<>();
final Map<Set<Requires.Modifier>, EnumSetBuilder<Requires.Modifier>>
= new HashMap<>();
final Map<Set<Exports.Modifier>, EnumSetBuilder<Exports.Modifier>>
= new HashMap<>();
final Map<Set<Opens.Modifier>, EnumSetBuilder<Opens.Modifier>>
= new HashMap<>();
private final int stringSetVar;
private final int enumSetVar;
private final IntSupplier localVarSupplier;
DedupSetBuilder(IntSupplier localVarSupplier) {
this.stringSetVar = localVarSupplier.getAsInt();
this.enumSetVar = localVarSupplier.getAsInt();
this.localVarSupplier = localVarSupplier;
}
void stringSet(Set<String> strings) {
stringSets.computeIfAbsent(strings,
s -> new SetBuilder<>(s, stringSetVar, localVarSupplier)
).increment();
}
void exportsModifiers(Set<Exports.Modifier> mods) {
exportsModifiersSets.computeIfAbsent(mods, s ->
new EnumSetBuilder<>(s, EXPORTS_MODIFIER_CLASSNAME,
enumSetVar, localVarSupplier)
).increment();
}
void opensModifiers(Set<Opens.Modifier> mods) {
opensModifiersSets.computeIfAbsent(mods, s ->
new EnumSetBuilder<>(s, OPENS_MODIFIER_CLASSNAME,
enumSetVar, localVarSupplier)
).increment();
}
void requiresModifiers(Set<Requires.Modifier> mods) {
requiresModifiersSets.computeIfAbsent(mods, s ->
new EnumSetBuilder<>(s, REQUIRES_MODIFIER_CLASSNAME,
enumSetVar, localVarSupplier)
).increment();
}
int indexOfStringSet(Set<String> names) {
return stringSets.get(names).build();
}
int indexOfExportsModifiers(Set<Exports.Modifier> mods) {
return exportsModifiersSets.get(mods).build();
}
int indexOfOpensModifiers(Set<Opens.Modifier> mods) {
return opensModifiersSets.get(mods).build();
}
int indexOfRequiresModifiers(Set<Requires.Modifier> mods) {
return requiresModifiersSets.get(mods).build();
}
int newStringSet(Set<String> names) {
int index = new SetBuilder<>(names, stringSetVar, localVarSupplier).build();
assert index == stringSetVar;
return index;
}
}
class SetBuilder<T> {
private final Set<T> elements;
private final int defaultVarIndex;
private final IntSupplier nextLocalVar;
private int refCount;
private int localVarIndex;
SetBuilder(Set<T> elements,
int defaultVarIndex,
IntSupplier nextLocalVar) {
this.elements = elements;
this.defaultVarIndex = defaultVarIndex;
this.nextLocalVar = nextLocalVar;
}
final void increment() {
refCount++;
}
void visitElement(T element, MethodVisitor mv) {
mv.visitLdcInsn(element);
}
final int build() {
int index = localVarIndex;
if (localVarIndex == 0) {
index = refCount <= 1 ? defaultVarIndex
: nextLocalVar.getAsInt();
if (index < MAX_LOCAL_VARS) {
localVarIndex = index;
} else {
index = defaultVarIndex;
}
generateSetOf(index);
}
return index;
}
private void generateSetOf(int index) {
if (elements.size() <= 10) {
StringBuilder sb = new StringBuilder("(");
for (T t : elements) {
sb.append("Ljava/lang/Object;");
visitElement(t, mv);
}
sb.append(")Ljava/util/Set;");
mv.visitMethodInsn(INVOKESTATIC, "java/util/Set",
"of", sb.toString(), true);
} else {
pushInt(mv, elements.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
int arrayIndex = 0;
for (T t : elements) {
mv.visitInsn(DUP);
pushInt(mv, arrayIndex);
visitElement(t, mv);
mv.visitInsn(AASTORE);
arrayIndex++;
}
mv.visitMethodInsn(INVOKESTATIC, "java/util/Set",
"of", "([Ljava/lang/Object;)Ljava/util/Set;", true);
}
mv.visitVarInsn(ASTORE, index);
}
}
class EnumSetBuilder<T> extends SetBuilder<T> {
private final String className;
EnumSetBuilder(Set<T> modifiers, String className,
int defaultVarIndex,
IntSupplier nextLocalVar) {
super(modifiers, defaultVarIndex, nextLocalVar);
this.className = className;
}
void visitElement(T t, MethodVisitor mv) {
mv.visitFieldInsn(GETSTATIC, className, t.toString(),
"L" + className + ";");
}
}
}
static ModuleFinder finderOf(Iterable<ModuleDescriptor> descriptors) {
Map<String, ModuleReference> namesToReference = new HashMap<>();
for (ModuleDescriptor descriptor : descriptors) {
String name = descriptor.name();
URI uri = URI.create("module:/" + name);
ModuleReference mref = new ModuleReference(descriptor, uri) {
@Override
public ModuleReader open() {
throw new UnsupportedOperationException();
}
};
namesToReference.putIfAbsent(name, mref);
}
return new ModuleFinder() {
@Override
public Optional<ModuleReference> find(String name) {
Objects.requireNonNull(name);
return Optional.ofNullable(namesToReference.get(name));
}
@Override
public Set<ModuleReference> findAll() {
return new HashSet<>(namesToReference.values());
}
};
}
}