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.Configuration;
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.lang.module.ResolvedModule;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
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.function.Supplier;
import java.util.stream.Collectors;
import jdk.internal.module.Checks;
import jdk.internal.module.DefaultRoots;
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.ModuleReferenceImpl;
import jdk.internal.module.ModuleResolution;
import jdk.internal.module.ModuleTarget;
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.ModuleVisitor;
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.Plugin;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
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 static final String SYSTEM_MODULES_MAP_CLASS =
"jdk/internal/module/SystemModulesMap";
private static final String SYSTEM_MODULES_CLASS_PREFIX =
"jdk/internal/module/SystemModules$";
private static final String ALL_SYSTEM_MODULES_CLASS =
SYSTEM_MODULES_CLASS_PREFIX + "all";
private static final String DEFAULT_SYSTEM_MODULES_CLASS =
SYSTEM_MODULES_CLASS_PREFIX + "default";
private boolean enabled;
public SystemModulesPlugin() {
this.enabled = true;
}
@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) {
throw new IllegalArgumentException(NAME + ": " + arg);
}
}
@Override
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
if (!enabled) {
throw new PluginException(NAME + " was set");
}
List<ModuleInfo> moduleInfos = transformModuleInfos(in, out);
Set<String> generated = genSystemModulesClasses(moduleInfos, out);
in.entries()
.filter(data -> !data.path().endsWith("/module-info.class")
&& !generated.contains(data.path()))
.forEach(data -> out.add(data));
return out.build();
}
List<ModuleInfo> transformModuleInfos(ResourcePool in, ResourcePoolBuilder out) {
List<ModuleInfo> moduleInfos = new ArrayList<>();
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 {
byte[] content = data.contentBytes();
Set<String> packages = module.packages();
ModuleInfo moduleInfo = new ModuleInfo(content, packages);
moduleInfo.validateNames();
moduleInfo.validatePackages();
if (moduleInfo.shouldRewrite()) {
data = data.copyWithContent(moduleInfo.getBytes());
}
moduleInfos.add(moduleInfo);
out.add(data);
} catch (IOException e) {
throw new PluginException(e);
}
});
return moduleInfos;
}
private Set<String> genSystemModulesClasses(List<ModuleInfo> moduleInfos,
ResourcePoolBuilder out) {
int moduleCount = moduleInfos.size();
ModuleFinder finder = finderOf(moduleInfos);
assert finder.findAll().size() == moduleCount;
Map<String, String> map = new LinkedHashMap<>();
Set<String> generated = new HashSet<>();
Set<String> allModuleNames = moduleInfos.stream()
.map(ModuleInfo::moduleName)
.collect(Collectors.toSet());
String rn = genSystemModulesClass(moduleInfos,
resolve(finder, allModuleNames),
ALL_SYSTEM_MODULES_CLASS,
out);
generated.add(rn);
String defaultSystemModulesClassName;
Configuration cf = resolve(finder, DefaultRoots.compute(finder));
if (cf.modules().size() == moduleCount) {
defaultSystemModulesClassName = ALL_SYSTEM_MODULES_CLASS;
} else {
defaultSystemModulesClassName = DEFAULT_SYSTEM_MODULES_CLASS;
rn = genSystemModulesClass(sublist(moduleInfos, cf),
cf,
defaultSystemModulesClassName,
out);
generated.add(rn);
}
int suffix = 0;
for (ModuleInfo mi : moduleInfos) {
if (mi.descriptor().mainClass().isPresent()) {
String moduleName = mi.moduleName();
cf = resolve(finder, Set.of(moduleName));
if (cf.modules().size() == moduleCount) {
map.put(moduleName, ALL_SYSTEM_MODULES_CLASS);
} else {
String cn = SYSTEM_MODULES_CLASS_PREFIX + (suffix++);
rn = genSystemModulesClass(sublist(moduleInfos, cf), cf, cn, out);
map.put(moduleName, cn);
generated.add(rn);
}
}
}
rn = genSystemModulesMapClass(ALL_SYSTEM_MODULES_CLASS,
defaultSystemModulesClassName,
map,
out);
generated.add(rn);
return generated;
}
private Configuration resolve(ModuleFinder finder, Set<String> roots) {
return Configuration.empty().resolveAndBind(finder, ModuleFinder.of(), roots);
}
private List<ModuleInfo> sublist(List<ModuleInfo> moduleInfos, Configuration cf) {
Set<String> names = cf.modules()
.stream()
.map(ResolvedModule::name)
.collect(Collectors.toSet());
return moduleInfos.stream()
.filter(mi -> names.contains(mi.moduleName()))
.collect(Collectors.toList());
}
private String genSystemModulesClass(List<ModuleInfo> moduleInfos,
Configuration cf,
String className,
ResourcePoolBuilder out) {
SystemModulesClassGenerator generator
= new SystemModulesClassGenerator(className, moduleInfos);
byte[] bytes = generator.getClassWriter(cf).toByteArray();
String rn = "/java.base/" + className + ".class";
ResourcePoolEntry e = ResourcePoolEntry.create(rn, bytes);
out.add(e);
return rn;
}
static class ModuleInfo {
private final ByteArrayInputStream bais;
private final Attributes attrs;
private final Set<String> packages;
private final boolean addModulePackages;
private ModuleDescriptor descriptor;
ModuleInfo(byte[] bytes, Set<String> packages) throws IOException {
this.bais = new ByteArrayInputStream(bytes);
this.packages = packages;
this.attrs = jdk.internal.module.ModuleInfo.read(bais, null);
this.descriptor = attrs.descriptor();
if (descriptor.isAutomatic()) {
throw new InternalError("linking automatic module is not supported");
}
this.addModulePackages = packages.size() > 0 && !hasModulePackages();
}
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> packages = new HashSet<>();
ClassVisitor cv = new ClassVisitor(Opcodes.ASM6) {
@Override
public ModuleVisitor visitModule(String name,
int flags,
String version) {
return new ModuleVisitor(Opcodes.ASM6) {
public void visitPackage(String pn) {
packages.add(pn);
}
};
}
};
try (InputStream in = getInputStream()) {
ClassReader cr = new ClassReader(in);
cr.accept(cv, 0);
return packages.size() > 0;
}
}
boolean shouldRewrite() {
return addModulePackages;
}
byte[] getBytes() throws IOException {
try (InputStream in = getInputStream()) {
if (shouldRewrite()) {
ModuleInfoRewriter rewriter = new ModuleInfoRewriter(in);
if (addModulePackages) {
rewriter.addModulePackages(packages);
}
byte[] bytes = rewriter.getBytes();
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
this.descriptor = ModuleDescriptor.read(bais);
}
return bytes;
} else {
return in.readAllBytes();
}
}
}
InputStream getInputStream() {
bais.reset();
return bais;
}
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);
}
}
byte[] getBytes() throws IOException {
extender.write(this);
return buf;
}
}
}
static class SystemModulesClassGenerator {
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 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 MethodVisitor mv;
private final String className;
private final List<ModuleInfo> moduleInfos;
private final DedupSetBuilder dedupSetBuilder
= new DedupSetBuilder(this::getNextLocalVar);
public SystemModulesClassGenerator(String className,
List<ModuleInfo> moduleInfos) {
this.className = className;
this.moduleInfos = moduleInfos;
moduleInfos.forEach(mi -> dedups(mi.descriptor()));
}
private int getNextLocalVar() {
return nextLocalVar++;
}
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(Configuration cf) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
+ ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8,
ACC_FINAL+ACC_SUPER,
className,
null,
"java/lang/Object",
new String[] { "jdk/internal/module/SystemModules" });
genConstructor(cw);
genHasSplitPackages(cw);
genIncubatorModules(cw);
genModuleDescriptorsMethod(cw);
genModuleTargetsMethod(cw);
genModuleHashesMethod(cw);
genModuleResolutionsMethod(cw);
genModuleReads(cw, cf);
genXXXPackagesToOpenMethods(cw);
return cw;
}
private void genConstructor(ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object",
"<init>",
"()V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void genHasSplitPackages(ClassWriter cw) {
boolean distinct = moduleInfos.stream()
.map(ModuleInfo::packages)
.flatMap(Set::stream)
.allMatch(new HashSet<>()::add);
boolean hasSplitPackages = !distinct;
mv = cw.visitMethod(ACC_PUBLIC,
"hasSplitPackages",
"()Z",
"()Z",
null);
mv.visitCode();
if (hasSplitPackages) {
mv.visitInsn(ICONST_1);
} else {
mv.visitInsn(ICONST_0);
}
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void genIncubatorModules(ClassWriter cw) {
boolean hasIncubatorModules = moduleInfos.stream()
.map(ModuleInfo::moduleResolution)
.filter(mres -> (mres != null && mres.hasIncubatingWarning()))
.findFirst()
.isPresent();
mv = cw.visitMethod(ACC_PUBLIC,
"hasIncubatorModules",
"()Z",
"()Z",
null);
mv.visitCode();
if (hasIncubatorModules) {
mv.visitInsn(ICONST_1);
} else {
mv.visitInsn(ICONST_0);
}
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void genModuleDescriptorsMethod(ClassWriter cw) {
this.mv = cw.visitMethod(ACC_PUBLIC,
"moduleDescriptors",
"()" + 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 genModuleTargetsMethod(ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
"moduleTargets",
"()" + 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);
ModuleInfo base = moduleInfos.get(0);
if (!base.moduleName().equals("java.base"))
throw new InternalError("java.base should be first module in list");
ModuleTarget target = base.target();
int count;
if (target != null && target.targetPlatform() != null) {
count = 1;
} else {
count = moduleInfos.size();
}
for (int index = 0; index < count; index++) {
ModuleInfo minfo = moduleInfos.get(index);
if (minfo.target() != null) {
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 genModuleHashesMethod(ClassWriter cw) {
MethodVisitor hmv =
cw.visitMethod(ACC_PUBLIC,
"moduleHashes",
"()" + 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(ClassWriter cw) {
MethodVisitor mresmv =
cw.visitMethod(ACC_PUBLIC,
"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 genModuleReads(ClassWriter cw, Configuration cf) {
Map<String, Set<String>> map = cf.modules().stream()
.collect(Collectors.toMap(
ResolvedModule::name,
m -> m.reads().stream()
.map(ResolvedModule::name)
.collect(Collectors.toSet())));
generate(cw, "moduleReads", map, true);
}
private void genXXXPackagesToOpenMethods(ClassWriter cw) {
ModuleFinder finder = finderOf(moduleInfos);
IllegalAccessMaps maps = IllegalAccessMaps.generate(finder);
generate(cw, "concealedPackagesToOpen", maps.concealedPackagesToOpen(), false);
generate(cw, "exportedPackagesToOpen", maps.exportedPackagesToOpen(), false);
}
private void generate(ClassWriter cw,
String methodName,
Map<String, Set<String>> map,
boolean dedup) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
methodName,
"()Ljava/util/Map;",
"()Ljava/util/Map;",
null);
mv.visitCode();
Map<Set<String>, Integer> locals;
if (dedup) {
Collection<Set<String>> values = map.values();
Set<Set<String>> duplicateSets = values.stream()
.distinct()
.filter(s -> Collections.frequency(values, s) > 1)
.collect(Collectors.toSet());
locals = new HashMap<>();
int index = 1;
for (Set<String> s : duplicateSets) {
genImmutableSet(mv, s);
mv.visitVarInsn(ASTORE, index);
locals.put(s, index);
if (++index >= MAX_LOCAL_VARS) {
break;
}
}
} else {
locals = Map.of();
}
pushInt(mv, map.size());
mv.visitTypeInsn(ANEWARRAY, "java/util/Map$Entry");
int index = 0;
for (Map.Entry<String, Set<String>> e : map.entrySet()) {
String name = e.getKey();
Set<String> s = e.getValue();
mv.visitInsn(DUP);
pushInt(mv, index);
mv.visitLdcInsn(name);
Integer varIndex = locals.get(s);
if (varIndex == null) {
genImmutableSet(mv, s);
} else {
mv.visitVarInsn(ALOAD, varIndex);
}
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();
}
private void genImmutableSet(MethodVisitor mv, Set<String> set) {
int size = set.size();
if (size > 2) {
pushInt(mv, size);
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
int i = 0;
for (String element : set) {
mv.visitInsn(DUP);
pushInt(mv, i);
mv.visitLdcInsn(element);
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 element : set) {
mv.visitLdcInsn(element);
sb.append("Ljava/lang/Object;");
}
sb.append(")Ljava/util/Set;");
mv.visitMethodInsn(INVOKESTATIC,
"java/util/Set",
"of",
sb.toString(),
true);
}
}
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 + ";");
}
}
}
private String genSystemModulesMapClass(String allSystemModulesClassName,
String defaultSystemModulesClassName,
Map<String, String> map,
ResourcePoolBuilder out) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
+ ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8,
ACC_FINAL+ACC_SUPER,
SYSTEM_MODULES_MAP_CLASS,
null,
"java/lang/Object",
null);
MethodVisitor mv = cw.visitMethod(0, "<init>", "()V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object",
"<init>",
"()V",
false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
mv = cw.visitMethod(ACC_STATIC,
"allSystemModules",
"()Ljdk/internal/module/SystemModules;",
"()Ljdk/internal/module/SystemModules;",
null);
mv.visitCode();
mv.visitTypeInsn(NEW, allSystemModulesClassName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL,
allSystemModulesClassName,
"<init>",
"()V",
false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
mv = cw.visitMethod(ACC_STATIC,
"defaultSystemModules",
"()Ljdk/internal/module/SystemModules;",
"()Ljdk/internal/module/SystemModules;",
null);
mv.visitCode();
mv.visitTypeInsn(NEW, defaultSystemModulesClassName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL,
defaultSystemModulesClassName,
"<init>",
"()V",
false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
mv = cw.visitMethod(ACC_STATIC,
"moduleNames",
"()[Ljava/lang/String;",
"()[Ljava/lang/String;",
null);
mv.visitCode();
pushInt(mv, map.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
int index = 0;
for (String moduleName : map.keySet()) {
mv.visitInsn(DUP);
pushInt(mv, index);
mv.visitLdcInsn(moduleName);
mv.visitInsn(AASTORE);
index++;
}
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
mv = cw.visitMethod(ACC_STATIC,
"classNames",
"()[Ljava/lang/String;",
"()[Ljava/lang/String;",
null);
mv.visitCode();
pushInt(mv, map.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
index = 0;
for (String className : map.values()) {
mv.visitInsn(DUP);
pushInt(mv, index);
mv.visitLdcInsn(className.replace('/', '.'));
mv.visitInsn(AASTORE);
index++;
}
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
String rn = "/java.base/" + SYSTEM_MODULES_MAP_CLASS + ".class";
ResourcePoolEntry e = ResourcePoolEntry.create(rn, cw.toByteArray());
out.add(e);
return rn;
}
private static void pushInt(MethodVisitor mv, int value) {
if (value <= 5) {
mv.visitInsn(ICONST_0 + value);
} else if (value < Byte.MAX_VALUE) {
mv.visitIntInsn(BIPUSH, value);
} else if (value < Short.MAX_VALUE) {
mv.visitIntInsn(SIPUSH, value);
} else {
throw new IllegalArgumentException("exceed limit: " + value);
}
}
private static ModuleFinder finderOf(Collection<ModuleInfo> moduleInfos) {
Supplier<ModuleReader> readerSupplier = () -> null;
Map<String, ModuleReference> namesToReference = new HashMap<>();
for (ModuleInfo mi : moduleInfos) {
String name = mi.moduleName();
ModuleReference mref
= new ModuleReferenceImpl(mi.descriptor(),
URI.create("jrt:/" + name),
readerSupplier,
null,
mi.target(),
null,
null,
mi.moduleResolution());
namesToReference.put(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());
}
};
}
}