| /* |
| * Copyright (c) 2015, 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; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import jdk.internal.misc.JavaLangModuleAccess; |
| import jdk.internal.misc.SharedSecrets; |
| import jdk.internal.module.Checks; |
| import jdk.internal.module.ModuleInfoExtender; |
| import jdk.internal.module.SystemModules; |
| 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.plugin.PluginException; |
| import jdk.tools.jlink.plugin.ModulePool; |
| import jdk.tools.jlink.plugin.Plugin; |
| import jdk.tools.jlink.internal.plugins.SystemModuleDescriptorPlugin.Builder.*; |
| import jdk.tools.jlink.plugin.ModuleEntry; |
| |
| /** |
| * Jlink plugin to reconstitute module descriptors for installed modules. |
| * It will extend module-info.class with ConcealedPackages 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 java.lang.module.SystemModuleFinder |
| * @see SystemModules |
| */ |
| public final class SystemModuleDescriptorPlugin implements Plugin { |
| private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess(); |
| |
| // TODO: packager has the dependency on the plugin name |
| // Keep it as "--installed-modules" until packager removes such |
| // dependency (should not need to specify this plugin since it |
| // is enabled by default) |
| private static final String NAME = "installed-modules"; |
| private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); |
| private boolean enabled; |
| |
| public SystemModuleDescriptorPlugin() { |
| 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 void configure(Map<String, String> config) { |
| if (config.containsKey(NAME)) { |
| enabled = false; |
| } |
| } |
| |
| |
| @Override |
| public void visit(ModulePool in, ModulePool out) { |
| if (!enabled) { |
| throw new PluginException(NAME + " was set"); |
| } |
| |
| Builder builder = new Builder(); |
| |
| // generate the byte code to create ModuleDescriptors |
| // skip parsing module-info.class and skip name check |
| in.modules().forEach(module -> { |
| Optional<ModuleEntry> optData = module.findEntry("module-info.class"); |
| if (! optData.isPresent()) { |
| // automatic module not supported yet |
| throw new PluginException("module-info.class not found for " + |
| module.getName() + " module"); |
| } |
| ModuleEntry data = optData.get(); |
| assert module.getName().equals(data.getModule()); |
| try { |
| ByteArrayInputStream bain = new ByteArrayInputStream(data.getBytes()); |
| ModuleDescriptor md = ModuleDescriptor.read(bain); |
| validateNames(md); |
| |
| ModuleDescriptorBuilder mbuilder = builder.module(md, module.getAllPackages()); |
| int packages = md.exports().size() + md.conceals().size(); |
| if (md.conceals().isEmpty() && |
| packages != module.getAllPackages().size()) { |
| // add ConcealedPackages attribute if not exist |
| bain.reset(); |
| ModuleInfoRewriter minfoWriter = |
| new ModuleInfoRewriter(bain, mbuilder.conceals()); |
| // replace with the overridden version |
| data = data.create(minfoWriter.getBytes()); |
| } |
| out.add(data); |
| } catch (IOException e) { |
| throw new PluginException(e); |
| } |
| }); |
| |
| // Generate the new class |
| ClassWriter cwriter = builder.build(); |
| in.entries().forEach(data -> { |
| if (data.getPath().endsWith("module-info.class")) |
| return; |
| if (builder.isOverriddenClass(data.getPath())) { |
| byte[] bytes = cwriter.toByteArray(); |
| ModuleEntry ndata = data.create(bytes); |
| out.add(ndata); |
| } else { |
| out.add(data); |
| } |
| }); |
| } |
| |
| /* |
| * Add ConcealedPackages attribute |
| */ |
| class ModuleInfoRewriter extends ByteArrayOutputStream { |
| final ModuleInfoExtender extender; |
| ModuleInfoRewriter(InputStream in, Set<String> conceals) throws IOException { |
| this.extender = ModuleInfoExtender.newExtender(in); |
| // Add ConcealedPackages attribute |
| this.extender.conceals(conceals); |
| this.extender.write(this); |
| } |
| |
| byte[] getBytes() { |
| return buf; |
| } |
| } |
| |
| void validateNames(ModuleDescriptor md) { |
| Checks.requireModuleName(md.name()); |
| for (Requires req : md.requires()) { |
| Checks.requireModuleName(req.name()); |
| } |
| for (Exports e : md.exports()) { |
| Checks.requirePackageName(e.source()); |
| if (e.isQualified()) |
| e.targets().forEach(Checks::requireModuleName); |
| } |
| for (Map.Entry<String, Provides> e : md.provides().entrySet()) { |
| String service = e.getKey(); |
| Provides provides = e.getValue(); |
| Checks.requireServiceTypeName(service); |
| Checks.requireServiceTypeName(provides.service()); |
| provides.providers().forEach(Checks::requireServiceProviderName); |
| } |
| for (String service : md.uses()) { |
| Checks.requireServiceTypeName(service); |
| } |
| for (String pn : md.conceals()) { |
| Checks.requirePackageName(pn); |
| } |
| } |
| |
| /* |
| * Returns the initial capacity for a new Set or Map of the given size |
| * to avoid resizing. |
| */ |
| static final int initialCapacity(int size) { |
| if (size == 0) { |
| return 0; |
| } else { |
| // Adjust to try and get size/capacity as close to the |
| // HashSet/HashMap default load factor without going over. |
| return (int)(Math.ceil((double)size / 0.75)); |
| } |
| } |
| |
| /** |
| * Builder of a new jdk.internal.module.SystemModules class |
| * to reconstitute ModuleDescriptor of the installed modules. |
| */ |
| static class Builder { |
| 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;"; |
| |
| // static variables in SystemModules class |
| private static final String MODULE_NAMES = "MODULE_NAMES"; |
| private static final String MODULES_TO_HASH = "MODULES_TO_HASH"; |
| private static final String PACKAGE_COUNT = "PACKAGES_IN_BOOT_LAYER"; |
| |
| private static final int BUILDER_VAR = 0; |
| private static final int MD_VAR = 1; // variable for ModuleDescriptor |
| private static final int MODS_VAR = 2; // variable for Set<Modifier> |
| private static final int STRING_SET_VAR = 3; // variable for Set<String> |
| private static final int MAX_LOCAL_VARS = 256; |
| |
| private final ClassWriter cw; |
| private MethodVisitor mv; |
| private int nextLocalVar = 4; |
| private int nextModulesIndex = 0; |
| |
| // list of all ModuleDescriptorBuilders, invoked in turn when building. |
| private final List<ModuleDescriptorBuilder> builders = new ArrayList<>(); |
| |
| // module name to hash |
| private final Map<String, String> modulesToHash = new HashMap<>(); |
| |
| // map Set<String> to a specialized builder to allow them to be |
| // deduplicated as they are requested |
| private final Map<Set<String>, StringSetBuilder> stringSets = new HashMap<>(); |
| |
| public Builder() { |
| this.cw = new ClassWriter(ClassWriter.COMPUTE_MAXS+ClassWriter.COMPUTE_FRAMES); |
| } |
| |
| /* |
| * static initializer initializing the static fields |
| * |
| * static Map<String, ModuleDescriptor> map = new HashMap<>(); |
| */ |
| private void clinit(int numModules, int numPackages) { |
| 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 String[] MODULES_TO_HASH = new String[] {....}; |
| cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC, MODULES_TO_HASH, |
| "[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(); |
| |
| this.mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", |
| null, null); |
| mv.visitCode(); |
| |
| // create the MODULE_NAMES array |
| pushInt(numModules); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); |
| |
| int index = 0; |
| for (ModuleDescriptorBuilder builder : builders) { |
| mv.visitInsn(DUP); // arrayref |
| pushInt(index++); |
| mv.visitLdcInsn(builder.md.name()); // value |
| mv.visitInsn(AASTORE); |
| } |
| |
| mv.visitFieldInsn(PUTSTATIC, CLASSNAME, MODULE_NAMES, |
| "[Ljava/lang/String;"); |
| |
| // create the MODULES_TO_HASH array |
| pushInt(numModules); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); |
| |
| index = 0; |
| for (ModuleDescriptorBuilder builder : builders) { |
| String mn = builder.md.name(); |
| String recordedHash = modulesToHash.get(mn); |
| if (recordedHash != null) { |
| mv.visitInsn(DUP); // arrayref |
| pushInt(index); |
| mv.visitLdcInsn(recordedHash); // value |
| mv.visitInsn(AASTORE); |
| } |
| index++; |
| } |
| |
| mv.visitFieldInsn(PUTSTATIC, CLASSNAME, MODULES_TO_HASH, |
| "[Ljava/lang/String;"); |
| |
| mv.visitInsn(RETURN); |
| mv.visitMaxs(0, 0); |
| mv.visitEnd(); |
| |
| } |
| |
| /* |
| * Adds the given ModuleDescriptor to the installed module list, and |
| * prepares mapping from Set<String> to StringSetBuilders to emit an |
| * optimized number of string sets during build. |
| */ |
| public ModuleDescriptorBuilder module(ModuleDescriptor md, Set<String> packages) { |
| ModuleDescriptorBuilder builder = new ModuleDescriptorBuilder(md, packages); |
| builders.add(builder); |
| |
| // exports |
| for (ModuleDescriptor.Exports e : md.exports()) { |
| if (e.isQualified()) { |
| stringSets.computeIfAbsent(e.targets(), s -> new StringSetBuilder(s)) |
| .increment(); |
| } |
| } |
| |
| // provides (preserve iteration order) |
| for (ModuleDescriptor.Provides p : md.provides().values()) { |
| stringSets.computeIfAbsent(p.providers(), s -> new StringSetBuilder(s, true)) |
| .increment(); |
| } |
| |
| // uses |
| stringSets.computeIfAbsent(md.uses(), s -> new StringSetBuilder(s)) |
| .increment(); |
| |
| // hashes |
| JLMA.hashes(md).ifPresent(mh -> modulesToHash.putAll(mh.hashes())); |
| |
| return builder; |
| } |
| |
| /* |
| * Generate bytecode for SystemModules |
| */ |
| public ClassWriter build() { |
| int numModules = builders.size(); |
| int numPackages = 0; |
| for (ModuleDescriptorBuilder builder : builders) { |
| numPackages += builder.md.packages().size(); |
| } |
| |
| this.clinit(numModules, numPackages); |
| this.mv = cw.visitMethod(ACC_PUBLIC+ACC_STATIC, |
| "modules", "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE, |
| "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE, null); |
| mv.visitCode(); |
| pushInt(numModules); |
| mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor"); |
| mv.visitVarInsn(ASTORE, MD_VAR); |
| |
| for (ModuleDescriptorBuilder builder : builders) { |
| builder.build(); |
| } |
| mv.visitVarInsn(ALOAD, MD_VAR); |
| mv.visitInsn(ARETURN); |
| mv.visitMaxs(0, 0); |
| mv.visitEnd(); |
| return cw; |
| } |
| |
| public boolean isOverriddenClass(String path) { |
| return path.equals("/java.base/" + CLASSNAME + ".class"); |
| } |
| |
| void pushInt(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 REQUIRES_MODIFIER_CLASSNAME = |
| "java/lang/module/ModuleDescriptor$Requires$Modifier"; |
| static final String REQUIRES_MODIFIER_TYPE = |
| "Ljava/lang/module/ModuleDescriptor$Requires$Modifier;"; |
| static final String BUILDER_TYPE = "Ljdk/internal/module/Builder;"; |
| static final String REQUIRES_MODIFIER_STRING_SIG = |
| "(" + REQUIRES_MODIFIER_TYPE + "Ljava/lang/String;)" + BUILDER_TYPE; |
| static final String STRING_SET_SIG = |
| "(Ljava/lang/String;Ljava/util/Set;)" + BUILDER_TYPE; |
| static final String SET_STRING_SIG = |
| "(Ljava/util/Set;Ljava/lang/String;)" + 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 STRING_STRING_SIG = |
| "(Ljava/lang/String;Ljava/lang/String;)" + BUILDER_TYPE; |
| |
| final ModuleDescriptor md; |
| final Set<String> packages; |
| |
| ModuleDescriptorBuilder(ModuleDescriptor md, Set<String> packages) { |
| this.md = md; |
| this.packages = packages; |
| } |
| |
| void newBuilder(String name, int reqs, int exports, int provides, |
| int packages) { |
| mv.visitTypeInsn(NEW, MODULE_DESCRIPTOR_BUILDER); |
| mv.visitInsn(DUP); |
| mv.visitLdcInsn(name); |
| pushInt(initialCapacity(reqs)); |
| pushInt(initialCapacity(exports)); |
| pushInt(initialCapacity(provides)); |
| pushInt(initialCapacity(packages)); |
| mv.visitMethodInsn(INVOKESPECIAL, MODULE_DESCRIPTOR_BUILDER, |
| "<init>", "(Ljava/lang/String;IIII)V", false); |
| mv.visitVarInsn(ASTORE, BUILDER_VAR); |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| } |
| |
| /* |
| * Returns the set of concealed packages from ModuleDescriptor, if present |
| * or compute it if the module does not have ConcealedPackages attribute |
| */ |
| Set<String> conceals() { |
| Set<String> conceals = md.conceals(); |
| if (conceals.isEmpty() && md.exports().size() != packages.size()) { |
| Set<String> exports = md.exports().stream() |
| .map(Exports::source) |
| .collect(Collectors.toSet()); |
| conceals = packages.stream() |
| .filter(pn -> !exports.contains(pn)) |
| .collect(Collectors.toSet()); |
| } |
| |
| if (conceals.size() + md.exports().size() != packages.size() && |
| // jdk.localedata may have concealed packages that don't exist |
| !md.name().equals("jdk.localedata")) { |
| throw new AssertionError(md.name() + ": conceals=" + conceals.size() + |
| ", exports=" + md.exports().size() + ", packages=" + packages.size()); |
| } |
| return conceals; |
| } |
| |
| void build() { |
| newBuilder(md.name(), md.requires().size(), |
| md.exports().size(), |
| md.provides().size(), |
| packages.size()); |
| |
| // requires |
| for (ModuleDescriptor.Requires req : md.requires()) { |
| switch (req.modifiers().size()) { |
| case 0: |
| requires(req.name()); |
| break; |
| case 1: |
| ModuleDescriptor.Requires.Modifier mod = |
| req.modifiers().iterator().next(); |
| requires(mod, req.name()); |
| break; |
| default: |
| requires(req.modifiers(), req.name()); |
| } |
| } |
| |
| // exports |
| for (ModuleDescriptor.Exports e : md.exports()) { |
| if (e.isQualified()) { |
| exports(e.source(), e.targets()); |
| } else { |
| exports(e.source()); |
| } |
| } |
| |
| // uses |
| uses(md.uses()); |
| |
| // provides |
| for (ModuleDescriptor.Provides p : md.provides().values()) { |
| provides(p.service(), p.providers()); |
| } |
| |
| // all packages |
| packages(packages); |
| |
| // version |
| md.version().ifPresent(this::version); |
| |
| // main class |
| md.mainClass().ifPresent(this::mainClass); |
| |
| // hashes |
| JLMA.hashes(md).ifPresent(mh -> { |
| algorithm(mh.algorithm()); |
| mh.names().forEach(mn -> moduleHash(mn, mh.hashFor(mn))); |
| }); |
| |
| putModuleDescriptor(); |
| } |
| |
| /* |
| * Put ModuleDescriptor into the modules array |
| */ |
| void putModuleDescriptor() { |
| mv.visitVarInsn(ALOAD, MD_VAR); |
| pushInt(nextModulesIndex++); |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "build", "()Ljava/lang/module/ModuleDescriptor;", false); |
| mv.visitInsn(AASTORE); |
| } |
| |
| /* |
| * Invoke Builder.requires(String mn) |
| */ |
| void requires(String name) { |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitLdcInsn(name); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "requires", STRING_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invoke Builder.requires(Modifier mod, String mn) |
| */ |
| void requires(ModuleDescriptor.Requires.Modifier mod, String name) { |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitFieldInsn(GETSTATIC, REQUIRES_MODIFIER_CLASSNAME, mod.name(), |
| REQUIRES_MODIFIER_TYPE); |
| mv.visitLdcInsn(name); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "requires", REQUIRES_MODIFIER_STRING_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invoke Builder.requires(Set<Modifier> mods, String mn) |
| * |
| * EnumSet<Modifier> mods = EnumSet.of(mod,....); |
| * Buidler.requires(mods, mn); |
| */ |
| void requires(Set<ModuleDescriptor.Requires.Modifier> mods, String name) { |
| mv.visitVarInsn(ALOAD, MODS_VAR); |
| String signature = "("; |
| for (ModuleDescriptor.Requires.Modifier m : mods) { |
| mv.visitFieldInsn(GETSTATIC, REQUIRES_MODIFIER_CLASSNAME, m.name(), |
| REQUIRES_MODIFIER_TYPE); |
| signature += "Ljava/util/Enum;"; |
| } |
| signature += ")Ljava/util/EnumSet;"; |
| mv.visitMethodInsn(INVOKESTATIC, "java/util/EnumSet", "of", |
| signature, false); |
| mv.visitVarInsn(ASTORE, MODS_VAR); |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitVarInsn(ALOAD, MODS_VAR); |
| mv.visitLdcInsn(name); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "requires", SET_STRING_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invoke Builder.exports(String pn) |
| */ |
| void exports(String pn) { |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| |
| mv.visitLdcInsn(pn); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "exports", STRING_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invoke Builder.exports(String pn, Set<String> targets) |
| * |
| * Set<String> targets = new HashSet<>(); |
| * targets.add(t); |
| * : |
| * : |
| * Builder.exports(pn, targets); |
| */ |
| void exports(String pn, Set<String> targets) { |
| int varIndex = stringSets.get(targets).build(); |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitLdcInsn(pn); |
| mv.visitVarInsn(ALOAD, varIndex); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "exports", STRING_SET_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invokes Builder.uses(Set<String> uses) |
| */ |
| void uses(Set<String> uses) { |
| int varIndex = stringSets.get(uses).build(); |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitVarInsn(ALOAD, varIndex); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "uses", SET_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invoke Builder.provides(String service, Set<String> providers) |
| * |
| * Set<String> providers = new LinkedHashSet<>(); |
| * providers.add(impl); |
| * : |
| * : |
| * Builder.exports(service, providers); |
| */ |
| void provides(String service, Set<String> providers) { |
| int varIndex = stringSets.get(providers).build(); |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitLdcInsn(service); |
| mv.visitVarInsn(ALOAD, varIndex); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "provides", STRING_SET_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| /* |
| * Invoke Builder.conceals(String pn) |
| */ |
| void packages(Set<String> packages) { |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| int varIndex = new StringSetBuilder(packages).build(); |
| assert varIndex == STRING_SET_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(ModuleDescriptor.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 algorithm(String alg) { |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitLdcInsn(alg); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "algorithm", STRING_SIG, false); |
| mv.visitInsn(POP); |
| } |
| |
| void moduleHash(String name, String hashString) { |
| mv.visitVarInsn(ALOAD, BUILDER_VAR); |
| mv.visitLdcInsn(name); |
| mv.visitLdcInsn(hashString); |
| mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER, |
| "moduleHash", STRING_STRING_SIG, false); |
| mv.visitInsn(POP); |
| } |
| } |
| |
| /* |
| * StringSetBuilder generates bytecode to create one single instance |
| * of HashSet for a given set of names and assign to a local variable |
| * slot. When there is only one single reference to a Set<String>, |
| * it will reuse STRING_SET_VAR for reference. For Set<String> with |
| * multiple references, it will use a new local variable. |
| */ |
| class StringSetBuilder { |
| final Set<String> names; |
| final boolean linked; |
| int refCount; |
| int localVarIndex; |
| |
| StringSetBuilder(Set<String> names, boolean linked) { |
| this.names = names; |
| this.linked = linked; |
| } |
| |
| StringSetBuilder(Set<String> names) { |
| this(names, false); |
| } |
| |
| void increment() { |
| refCount++; |
| } |
| |
| /* |
| * Build bytecode for the Set<String> 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. |
| */ |
| 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 ? STRING_SET_VAR |
| : nextLocalVar++; |
| if (index < MAX_LOCAL_VARS) { |
| localVarIndex = index; |
| } else { |
| // overflow: disable optimization and keep localVarIndex = 0 |
| index = STRING_SET_VAR; |
| } |
| |
| if (names.isEmpty()) { |
| mv.visitMethodInsn(INVOKESTATIC, "java/util/Collections", |
| "emptySet", "()Ljava/util/Set;", false); |
| mv.visitVarInsn(ASTORE, index); |
| } else if (names.size() == 1) { |
| mv.visitLdcInsn(names.iterator().next()); |
| mv.visitMethodInsn(INVOKESTATIC, "java/util/Collections", |
| "singleton", "(Ljava/lang/Object;)Ljava/util/Set;", false); |
| mv.visitVarInsn(ASTORE, index); |
| } else { |
| String cn = linked ? "java/util/LinkedHashSet" : "java/util/HashSet"; |
| mv.visitTypeInsn(NEW, cn); |
| mv.visitInsn(DUP); |
| pushInt(initialCapacity(names.size())); |
| mv.visitMethodInsn(INVOKESPECIAL, cn, "<init>", "(I)V", false); |
| |
| mv.visitVarInsn(ASTORE, index); |
| for (String t : names) { |
| mv.visitVarInsn(ALOAD, index); |
| mv.visitLdcInsn(t); |
| mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Set", |
| "add", "(Ljava/lang/Object;)Z", true); |
| mv.visitInsn(POP); |
| } |
| } |
| } |
| return index; |
| } |
| } |
| } |
| } |