blob: 5e1e64e39393e4850f8a69e860a96a150feea9db [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.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;
/**
* Jlink plugin to reconstitute module descriptors for system modules.
* It will extend module-info.class with ModulePackages attribute,
* if not present. It also determines the number of packages of
* the boot layer at link time.
*
* This plugin will override jdk.internal.module.SystemModules class
*
* @see jdk.internal.module.SystemModuleFinder
* @see SystemModules
*/
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);
// generate the byte code to create ModuleDescriptors
// such that the modules linked in the image would skip parsing
// of module-info.class and also skip name check
// Sort modules in the topological order so that java.base is always first.
new ModuleSorter(in.moduleView()).sorted().forEach(module -> {
ResourcePoolEntry data = module.findEntry("module-info.class").orElseThrow(
// automatic module not supported yet
() -> new PluginException("module-info.class not found for " +
module.name() + " module")
);
assert module.name().equals(data.moduleName());
try {
// validate the module and add to system modules
data = generator.buildModuleInfo(data, module.packages());
// add resource pool entry
out.add(data);
} catch (IOException e) {
throw new PluginException(e);
}
});
// Generate the new class
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; // may be different that the original one
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);
// If ModulePackages attribute is present, the packages from this
// module descriptor returns the packages in that attribute.
// If it's not present, ModuleDescriptor::packages only contains
// the exported and open packages from module-info.class
this.descriptor = attrs.descriptor();
if (descriptor.isAutomatic()) {
throw new InternalError("linking automatic module is not supported");
}
// add ModulePackages attribute if this module contains some packages
// and ModulePackages is not present
this.addModulePackages = packages.size() > 0 && !hasModulePackages();
// drop target attribute only if any OS property is present
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();
}
/**
* Validates names in ModuleDescriptor
*/
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);
}
}
/**
* Validates if exported and open packages are present
*/
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);
}
};
// prototype of attributes that should be parsed
Attribute[] attrs = new Attribute[] {
new ClassFileAttributes.ModulePackagesAttribute()
};
try (InputStream in = getInputStream()) {
// parse module-info.class
ClassReader cr = new ClassReader(in);
cr.accept(cv, attrs, 0);
return attrTypes.contains(ClassFileConstants.MODULE_PACKAGES);
}
}
/**
* Returns true if module-info.class should be written
* 1. add ModulePackages attribute if not present; or
* 2. drop ModuleTarget attribute except java.base
*/
boolean shouldRewrite() {
return addModulePackages || dropModuleTarget;
}
/**
* Returns the bytes for the module-info.class with ModulePackages
* attribute added and/or with ModuleTarget attribute dropped.
*/
byte[] getBytes() throws IOException {
try (InputStream in = getInputStream()) {
if (shouldRewrite()) {
ModuleInfoRewriter rewriter = new ModuleInfoRewriter(in);
if (addModulePackages) {
rewriter.addModulePackages(packages);
}
if (dropModuleTarget) {
rewriter.dropModuleTarget();
}
// rewritten module descriptor
byte[] bytes = rewriter.getBytes();
try (ByteArrayInputStream bain = new ByteArrayInputStream(bytes)) {
this.descriptor = ModuleDescriptor.read(bain);
}
return bytes;
} else {
return in.readAllBytes();
}
}
}
/*
* Returns the input stream of the module-info.class
*/
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) {
// Add ModulePackages attribute
if (packages.size() > 0) {
extender.packages(packages);
}
}
void dropModuleTarget() {
extender.targetPlatform("");
}
byte[] getBytes() throws IOException {
extender.write(this);
return buf;
}
}
}
/**
* ClassWriter of a new jdk.internal.module.SystemModules class
* to reconstitute ModuleDescriptor of the system modules.
*/
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;";
// static variables in SystemModules class
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; // variable for ModuleDescriptor
private final int MT_VAR = 1; // variable for ModuleTarget
private final int MH_VAR = 1; // variable for ModuleHashes
private int nextLocalVar = 2; // index to next local variable
private final ClassWriter cw;
private boolean dropModuleTarget;
// Method visitor for generating the SystemModules::modules() method
private MethodVisitor mv;
// list of all ModuleDescriptorBuilders, invoked in turn when building.
private final List<ModuleInfo> moduleInfos = new ArrayList<>();
// A builder to create one single Set instance for a given set of
// names or modifiers to reduce the footprint
// e.g. target modules of qualified exports
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++;
}
/*
* static initializer initializing the static fields
*
* static Map<String, ModuleDescriptor> map = new HashMap<>();
*/
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);
// public static String[] MODULE_NAMES = new String[] {....};
cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC, MODULE_NAMES,
"[Ljava/lang/String;", null, null)
.visitEnd();
// public static int PACKAGES_IN_BOOT_LAYER;
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();
// create the MODULE_NAMES array
pushInt(clinit, numModules);
clinit.visitTypeInsn(ANEWARRAY, "java/lang/String");
int index = 0;
for (ModuleInfo minfo : moduleInfos) {
clinit.visitInsn(DUP); // arrayref
pushInt(clinit, index++);
clinit.visitLdcInsn(minfo.moduleName()); // value
clinit.visitInsn(AASTORE);
}
clinit.visitFieldInsn(PUTSTATIC, CLASSNAME, MODULE_NAMES,
"[Ljava/lang/String;");
clinit.visitInsn(RETURN);
clinit.visitMaxs(0, 0);
clinit.visitEnd();
// public static boolean hasSplitPackages();
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();
}
/*
* Adds the given ModuleDescriptor to the system module list.
* It performs link-time validation and prepares mapping from various
* Sets to SetBuilders to emit an optimized number of sets during build.
*/
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;
// drop ModuleTarget attribute if java.base has all OS properties
ModuleTarget target = moduleInfo.target();
if (dropModuleTarget && target.targetPlatform() != null) {
dropModuleTarget = true;
} else {
dropModuleTarget = false;
}
} else {
moduleInfo = new ModuleInfo(entry.contentBytes(), packages, dropModuleTarget);
}
// link-time validation
moduleInfo.validateNames();
// check if any exported or open package is not present
moduleInfo.validatePackages();
// module-info.class may be overridden for optimization
// 1. update ModuleTarget attribute to drop targetPlartform
// 2. add/update ModulePackages attribute
if (moduleInfo.shouldRewrite()) {
entry = entry.copyWithContent(moduleInfo.getBytes());
}
moduleInfos.add(moduleInfo);
dedups(moduleInfo.descriptor());
return entry;
}
private void dedups(ModuleDescriptor md) {
// exports
for (Exports e : md.exports()) {
dedupSetBuilder.stringSet(e.targets());
dedupSetBuilder.exportsModifiers(e.modifiers());
}
// opens
for (Opens opens : md.opens()) {
dedupSetBuilder.stringSet(opens.targets());
dedupSetBuilder.opensModifiers(opens.modifiers());
}
// requires
for (Requires r : md.requires()) {
dedupSetBuilder.requiresModifiers(r.modifiers());
}
// uses
dedupSetBuilder.stringSet(md.uses());
}
/*
* Generate bytecode for SystemModules
*/
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);
// generate SystemModules::descriptors
genDescriptorsMethod();
// generate SystemModules::targets
genTargetsMethod();
// generate SystemModules::hashes
genHashesMethod();
// generate SystemModules::moduleResolutions
genModuleResolutionsMethod();
// generate SystemModules::concealedPackagesToOpen and
// SystemModules::exportedPackagesToOpen
genXXXPackagesToOpenMethods();
return cw;
}
/**
* Generate bytecode for SystemModules::descriptors method
*/
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();
}
/**
* Generate bytecode for SystemModules::targets method
*/
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);
// new ModuleTarget(String, String)
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();
}
/**
* Generate bytecode for SystemModules::hashes method
*/
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();
}
/**
* Generate bytecode for SystemModules::methodResoultions method
*/
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();
}
/**
* Generate SystemModules::concealedPackagesToOpen and
* SystemModules::exportedPackagesToOpen methods.
*/
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());
}
/**
* Generate SystemModules:XXXPackagesToOpen
*/
private void generate(String methodName, Map<String, Set<String>> map) {
// Map<String, Set<String>> XXXPackagesToOpen()
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC+ACC_STATIC,
methodName,
"()Ljava/util/Map;",
"()Ljava/util/Map;",
null);
mv.visitCode();
// new Map$Entry[moduleCount]
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);
// use Set.of(Object[]) when there are more than 2 packages
// use Set.of(Object) or Set.of(Object, Object) when fewer packages
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++;
}
// invoke Map.ofEntries(Map$Entry[])
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;";
// method signature for static Builder::newExports, newOpens,
// newProvides, newRequires methods
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;
// method signature for Builder instance methods that
// return this Builder instance
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() {
// new jdk.internal.module.Builder
newBuilder();
// requires
requires(md.requires());
// exports
exports(md.exports());
// opens
opens(md.opens());
// uses
uses(md.uses());
// provides
provides(md.provides());
// all packages
packages(packages);
// version
md.version().ifPresent(this::version);
// main class
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);
}
}
/*
* Invoke Builder.<methodName>(boolean value)
*/
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);
}
/*
* Put ModuleDescriptor into the modules array
*/
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);
}
/*
* Call Builder::newRequires to create Requires instances and
* then pass it to the builder by calling:
* Builder.requires(Requires[])
*
*/
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); // arrayref
pushInt(mv, arrayIndex++);
newRequires(require.modifiers(), require.name(), compiledVersion);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"requires", REQUIRES_ARRAY_SIG, false);
}
/*
* Invoke Builder.newRequires(Set<Modifier> mods, String mn, String compiledVersion)
*
* Set<Modifier> mods = ...
* Builder.newRequires(mods, mn, compiledVersion);
*/
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);
}
}
/*
* Call Builder::newExports to create Exports instances and
* then pass it to the builder by calling:
* Builder.exports(Exports[])
*
*/
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); // arrayref
pushInt(mv, arrayIndex++);
newExports(export.modifiers(), export.source(), export.targets());
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"exports", EXPORTS_ARRAY_SIG, false);
}
/*
* Invoke
* Builder.newExports(Set<Exports.Modifier> ms, String pn,
* Set<String> targets)
* or
* Builder.newExports(Set<Exports.Modifier> ms, String pn)
*
* Set<String> targets = new HashSet<>();
* targets.add(t);
* :
* :
*
* Set<Modifier> mods = ...
* Builder.newExports(mods, pn, targets);
*/
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);
}
}
/**
* Call Builder::newOpens to create Opens instances and
* then pass it to the builder by calling:
* Builder.opens(Opens[])
*/
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); // arrayref
pushInt(mv, arrayIndex++);
newOpens(open.modifiers(), open.source(), open.targets());
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"opens", OPENS_ARRAY_SIG, false);
}
/*
* Invoke
* Builder.newOpens(Set<Opens.Modifier> ms, String pn,
* Set<String> targets)
* or
* Builder.newOpens(Set<Opens.Modifier> ms, String pn)
*
* Set<String> targets = new HashSet<>();
* targets.add(t);
* :
* :
*
* Set<Modifier> mods = ...
* Builder.newOpens(mods, pn, targets);
*/
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);
}
}
/*
* Invoke Builder.uses(Set<String> uses)
*/
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);
}
/*
* Call Builder::newProvides to create Provides instances and
* then pass it to the builder by calling:
* Builder.provides(Provides[] provides)
*
*/
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); // arrayref
pushInt(mv, arrayIndex++);
newProvides(provide.service(), provide.providers());
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
"provides", PROVIDES_ARRAY_SIG, false);
}
/*
* Invoke Builder.newProvides(String service, Set<String> providers)
*
* Set<String> providers = new HashSet<>();
* providers.add(impl);
* :
* :
* Builder.newProvides(service, providers);
*/
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); // arrayref
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);
}
/*
* Invoke Builder.packages(String pn)
*/
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);
}
/*
* Invoke Builder.mainClass(String cn)
*/
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);
}
/*
* Invoke Builder.version(Version v);
*/
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;
}
/**
* Build ModuleHashes
*/
void build() {
if (recordedHashes == null)
return;
// new jdk.internal.module.ModuleHashes.Builder
newModuleHashesBuilder();
// Invoke ModuleHashes.Builder::hashForModule
recordedHashes
.names()
.forEach(mn -> hashForModule(mn, recordedHashes.hashFor(mn)));
// Put ModuleHashes into the hashes array
pushModuleHashes();
}
/*
* Create ModuleHashes.Builder instance
*/
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);
}
/*
* Invoke ModuleHashes.Builder::build and put the returned
* ModuleHashes to the hashes array
*/
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);
}
/*
* Invoke ModuleHashes.Builder.hashForModule(String name, byte[] hash);
*/
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); // arrayref
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);
}
}
/*
* Wraps set creation, ensuring identical sets are properly deduplicated.
*/
class DedupSetBuilder {
// map Set<String> to a specialized builder to allow them to be
// deduplicated as they are requested
final Map<Set<String>, SetBuilder<String>> stringSets = new HashMap<>();
// map Set<Requires.Modifier> to a specialized builder to allow them to be
// deduplicated as they are requested
final Map<Set<Requires.Modifier>, EnumSetBuilder<Requires.Modifier>>
requiresModifiersSets = new HashMap<>();
// map Set<Exports.Modifier> to a specialized builder to allow them to be
// deduplicated as they are requested
final Map<Set<Exports.Modifier>, EnumSetBuilder<Exports.Modifier>>
exportsModifiersSets = new HashMap<>();
// map Set<Opens.Modifier> to a specialized builder to allow them to be
// deduplicated as they are requested
final Map<Set<Opens.Modifier>, EnumSetBuilder<Opens.Modifier>>
opensModifiersSets = 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;
}
/*
* Add the given set of strings to this builder.
*/
void stringSet(Set<String> strings) {
stringSets.computeIfAbsent(strings,
s -> new SetBuilder<>(s, stringSetVar, localVarSupplier)
).increment();
}
/*
* Add the given set of Exports.Modifiers
*/
void exportsModifiers(Set<Exports.Modifier> mods) {
exportsModifiersSets.computeIfAbsent(mods, s ->
new EnumSetBuilder<>(s, EXPORTS_MODIFIER_CLASSNAME,
enumSetVar, localVarSupplier)
).increment();
}
/*
* Add the given set of Opens.Modifiers
*/
void opensModifiers(Set<Opens.Modifier> mods) {
opensModifiersSets.computeIfAbsent(mods, s ->
new EnumSetBuilder<>(s, OPENS_MODIFIER_CLASSNAME,
enumSetVar, localVarSupplier)
).increment();
}
/*
* Add the given set of Requires.Modifiers
*/
void requiresModifiers(Set<Requires.Modifier> mods) {
requiresModifiersSets.computeIfAbsent(mods, s ->
new EnumSetBuilder<>(s, REQUIRES_MODIFIER_CLASSNAME,
enumSetVar, localVarSupplier)
).increment();
}
/*
* Retrieve the index to the given set of Strings. Emit code to
* generate it when SetBuilder::build is called.
*/
int indexOfStringSet(Set<String> names) {
return stringSets.get(names).build();
}
/*
* Retrieve the index to the given set of Exports.Modifier.
* Emit code to generate it when EnumSetBuilder::build is called.
*/
int indexOfExportsModifiers(Set<Exports.Modifier> mods) {
return exportsModifiersSets.get(mods).build();
}
/**
* Retrieve the index to the given set of Opens.Modifier.
* Emit code to generate it when EnumSetBuilder::build is called.
*/
int indexOfOpensModifiers(Set<Opens.Modifier> mods) {
return opensModifiersSets.get(mods).build();
}
/*
* Retrieve the index to the given set of Requires.Modifier.
* Emit code to generate it when EnumSetBuilder::build is called.
*/
int indexOfRequiresModifiers(Set<Requires.Modifier> mods) {
return requiresModifiersSets.get(mods).build();
}
/*
* Build a new string set without any attempt to deduplicate it.
*/
int newStringSet(Set<String> names) {
int index = new SetBuilder<>(names, stringSetVar, localVarSupplier).build();
assert index == stringSetVar;
return index;
}
}
/*
* SetBuilder generates bytecode to create one single instance of Set
* for a given set of elements and assign to a local variable slot.
* When there is only one single reference to a Set<T>,
* it will reuse defaultVarIndex. For a Set with multiple references,
* it will use a new local variable retrieved from the nextLocalVar
*/
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;
}
/*
* Increments the number of references to this particular set.
*/
final void increment() {
refCount++;
}
/**
* Generate the appropriate instructions to load an object reference
* to the element onto the stack.
*/
void visitElement(T element, MethodVisitor mv) {
mv.visitLdcInsn(element);
}
/*
* Build bytecode for the Set represented by this builder,
* or get the local variable index of a previously generated set
* (in the local scope).
*
* @return local variable index of the generated set.
*/
final int build() {
int index = localVarIndex;
if (localVarIndex == 0) {
// if non-empty and more than one set reference this builder,
// emit to a unique local
index = refCount <= 1 ? defaultVarIndex
: nextLocalVar.getAsInt();
if (index < MAX_LOCAL_VARS) {
localVarIndex = index;
} else {
// overflow: disable optimization by using localVarIndex = 0
index = defaultVarIndex;
}
generateSetOf(index);
}
return index;
}
private void generateSetOf(int index) {
if (elements.size() <= 10) {
// call Set.of(e1, e2, ...)
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 {
// call Set.of(E... elements)
pushInt(mv, elements.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
int arrayIndex = 0;
for (T t : elements) {
mv.visitInsn(DUP); // arrayref
pushInt(mv, arrayIndex);
visitElement(t, mv); // value
mv.visitInsn(AASTORE);
arrayIndex++;
}
mv.visitMethodInsn(INVOKESTATIC, "java/util/Set",
"of", "([Ljava/lang/Object;)Ljava/util/Set;", true);
}
mv.visitVarInsn(ASTORE, index);
}
}
/*
* Generates bytecode to create one single instance of EnumSet
* for a given set of modifiers and assign to a local variable slot.
*/
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;
}
/**
* Loads an Enum field.
*/
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());
}
};
}
}