Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compute jar module descriptors in parallel #57

Merged
merged 4 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/main/java/cpw/mods/cl/JarModuleFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public class JarModuleFinder implements ModuleFinder {
private final Map<String, ModuleReference> moduleReferenceMap;

JarModuleFinder(final SecureJar... jars) {
record ref(SecureJar.ModuleDataProvider jar, ModuleReference ref) {}
this.moduleReferenceMap = Arrays.stream(jars)
.map(jar->new ref(jar.moduleDataProvider(), new JarModuleReference(jar.moduleDataProvider())))
.collect(Collectors.toMap(r->r.jar.name(), r->r.ref, (r1, r2)->r1));
// Computing the module descriptor can be slow so do it in parallel!
// Jars are not thread safe internally, but they are independent, so this is safe.
.parallel()
// Note: Collectors.toMap() works fine with parallel streams.
.collect(Collectors.toMap(jar -> jar.moduleDataProvider().name(), jar -> new JarModuleReference(jar.moduleDataProvider()), (r1, r2) -> r1));
}

@Override
Expand Down
59 changes: 41 additions & 18 deletions src/main/java/cpw/mods/jarhandling/JarMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import cpw.mods.jarhandling.impl.ModuleJarMetadata;
import cpw.mods.jarhandling.impl.SimpleJarMetadata;
import org.jetbrains.annotations.Nullable;

import java.lang.module.ModuleDescriptor;
import java.nio.file.Path;
Expand All @@ -11,8 +12,20 @@

public interface JarMetadata {
String name();
@Nullable
String version();
ModuleDescriptor descriptor();

/**
* {@return the provider declarations for this jar}
*
* <p>Computing the {@link #descriptor()} can be expensive as it requires scanning the jar for packages.
* If only the service providers are needed, this method can be used instead.
*/
default List<SecureJar.Provider> providers() {
return descriptor().provides().stream().map(p -> new SecureJar.Provider(p.service(), p.providers())).toList();
}

// ALL from jdk.internal.module.ModulePath.java
Pattern DASH_VERSION = Pattern.compile("-([.\\d]+)");
Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
Expand Down Expand Up @@ -41,23 +54,24 @@ public interface JarMetadata {
* from {@code Automatic-Module-Name} in the manifest.
*/
static JarMetadata from(JarContents jar) {
final var pkgs = jar.getPackages();
var mi = jar.findFile("module-info.class");
if (mi.isPresent()) {
return new ModuleJarMetadata(mi.get(), pkgs);
return new ModuleJarMetadata(mi.get(), jar::getPackages);
} else {
var providers = jar.getMetaInfServices();
var fileCandidate = fromFileName(jar.getPrimaryPath(), pkgs, providers);
var aname = jar.getManifest().getMainAttributes().getValue("Automatic-Module-Name");
if (aname != null) {
return new SimpleJarMetadata(aname, fileCandidate.version(), pkgs, providers);
} else {
return fileCandidate;
var nav = computeNameAndVersion(jar.getPrimaryPath());
String name = nav.name();
String version = nav.version();

String automaticModuleName = jar.getManifest().getMainAttributes().getValue("Automatic-Module-Name");
if (automaticModuleName != null) {
name = automaticModuleName;
}

return new SimpleJarMetadata(name, version, jar::getPackages, jar.getMetaInfServices());
}
}

static SimpleJarMetadata fromFileName(final Path path, final Set<String> pkgs, final List<SecureJar.Provider> providers) {
private static NameAndVersion computeNameAndVersion(Path path) {
// detect Maven-like paths
Path versionMaybe = path.getParent();
if (versionMaybe != null)
Expand All @@ -73,29 +87,29 @@ static SimpleJarMetadata fromFileName(final Path path, final Set<String> pkgs, f
if (mat.find()) {
var potential = ver.substring(mat.start());
ver = safeParseVersion(potential, path.getFileName().toString());
return new SimpleJarMetadata(cleanModuleName(name), ver, pkgs, providers);
return new NameAndVersion(cleanModuleName(name), ver);
} else {
return new SimpleJarMetadata(cleanModuleName(name), null, pkgs, providers);
return new NameAndVersion(cleanModuleName(name), null);
}
}
}
}

// fallback parsing
var fn = path.getFileName().toString();
var fn = path.getFileName().toString();
var lastDot = fn.lastIndexOf('.');
if (lastDot > 0) {
fn = fn.substring(0, lastDot); // strip extension if possible
}

var mat = DASH_VERSION.matcher(fn);
if (mat.find()) {
var potential = fn.substring(mat.start() + 1);
var ver = safeParseVersion(potential, path.getFileName().toString());
var name = mat.replaceAll("");
return new SimpleJarMetadata(cleanModuleName(name), ver, pkgs, providers);
return new NameAndVersion(cleanModuleName(name), ver);
} else {
return new SimpleJarMetadata(cleanModuleName(fn), null, pkgs, providers);
return new NameAndVersion(cleanModuleName(fn), null);
}
}

Expand Down Expand Up @@ -144,6 +158,15 @@ private static String cleanModuleName(String mn) {
return mn;
}

/**
* @deprecated Build from jar contents directly using {@link #from(JarContents)}.
*/
@Deprecated(forRemoval = true, since = "2.1.23")
static SimpleJarMetadata fromFileName(final Path path, final Set<String> pkgs, final List<SecureJar.Provider> providers) {
var nav = computeNameAndVersion(path);
return new SimpleJarMetadata(nav.name(), nav.version(), () -> pkgs, providers);
}

/**
* @deprecated Use {@link #from(JarContents)} instead.
*/
Expand All @@ -153,13 +176,13 @@ static JarMetadata from(final SecureJar jar, final Path... path) {
final var pkgs = jar.getPackages();
var mi = jar.moduleDataProvider().findFile("module-info.class");
if (mi.isPresent()) {
return new ModuleJarMetadata(mi.get(), pkgs);
return new ModuleJarMetadata(mi.get(), jar::getPackages);
} else {
var providers = jar.getProviders();
var fileCandidate = fromFileName(path[0], pkgs, providers);
var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue("Automatic-Module-Name");
if (aname != null) {
return new SimpleJarMetadata(aname, fileCandidate.version(), pkgs, providers);
return new SimpleJarMetadata(aname, fileCandidate.version(), () -> pkgs, providers);
} else {
return fileCandidate;
}
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/cpw/mods/jarhandling/LazyJarMetadata.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cpw.mods.jarhandling;

import org.jetbrains.annotations.Nullable;

import java.lang.module.ModuleDescriptor;

/**
* Base class for {@link JarMetadata} implementations that lazily compute their descriptor.
* This is recommended because descriptor computation can then run in parallel.
*/
public abstract class LazyJarMetadata implements JarMetadata {
@Nullable
private ModuleDescriptor descriptor;

@Override
public final ModuleDescriptor descriptor() {
if (descriptor == null) {
descriptor = computeDescriptor();
}
return descriptor;
}

/**
* Computes the module descriptor for this jar.
* This method is called at most once.
*/
protected abstract ModuleDescriptor computeDescriptor();
}
9 changes: 9 additions & 0 deletions src/main/java/cpw/mods/jarhandling/NameAndVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cpw.mods.jarhandling;

import org.jetbrains.annotations.Nullable;

/**
* Holder class for name and version of a module, used in {@link JarMetadata} computations.
* Unfortunately, interfaces cannot hold private records (yet?).
*/
record NameAndVersion(String name, @Nullable String version) {}
46 changes: 31 additions & 15 deletions src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cpw.mods.jarhandling.impl;

import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.LazyJarMetadata;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ModuleVisitor;
Expand All @@ -15,29 +17,42 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

/**
* {@link JarMetadata} implementation for a modular jar.
* Reads the module descriptor from the jar.
*/
public class ModuleJarMetadata implements JarMetadata {
private final ModuleDescriptor descriptor;
public class ModuleJarMetadata extends LazyJarMetadata {
private final ModuleClassVisitor mcv;
private final Supplier<Set<String>> packagesSupplier;

public ModuleJarMetadata(final URI uri, final Set<String> packages) {
public ModuleJarMetadata(URI uri, Supplier<Set<String>> packagesSupplier) {
try (var is = Files.newInputStream(Path.of(uri))) {
ClassReader cr = new ClassReader(is);
var mcv = new ModuleClassVisitor();
cr.accept(mcv, ClassReader.SKIP_CODE);
mcv.mfv().packages().addAll(packages);
mcv.mfv().builder().packages(mcv.mfv.packages());
descriptor = mcv.mfv().builder().build();
this.mcv = mcv;
} catch (IOException e) {
throw new UncheckedIOException(e);
}

// Defer package scanning until computeDescriptor()
this.packagesSupplier = packagesSupplier;
}

private class ModuleClassVisitor extends ClassVisitor {
@Override
protected ModuleDescriptor computeDescriptor() {
mcv.mfv().packages().addAll(packagesSupplier.get());
mcv.mfv().builder().packages(mcv.mfv().packages());
return mcv.mfv().builder().build();
}

private static class ModuleClassVisitor extends ClassVisitor {
private ModFileVisitor mfv;
private String name;
@Nullable
private String version;

ModuleClassVisitor() {
super(Opcodes.ASM9);
Expand All @@ -46,19 +61,23 @@ private class ModuleClassVisitor extends ClassVisitor {
@Override
public ModuleVisitor visitModule(final String name, final int access, final String version) {
this.mfv = new ModFileVisitor(name, access, version);
this.name = name;
this.version = version;
return this.mfv;
}

public ModFileVisitor mfv() {
return mfv;
}
}
private class ModFileVisitor extends ModuleVisitor {

private static class ModFileVisitor extends ModuleVisitor {
private final ModuleDescriptor.Builder builder;
private final Set<String> packages = new HashSet<>();

public ModFileVisitor(final String name, final int access, final String version) {
super(Opcodes.ASM9);
// Note: we ignore the access flags and make every module open instead!
builder = ModuleDescriptor.newOpenModule(name);
if (version != null) builder.version(version);
}
Expand Down Expand Up @@ -127,18 +146,15 @@ public Set<String> packages() {
return packages;
}
}

@Override
public String name() {
return descriptor.name();
return mcv.name;
}

@Override
@Nullable
public String version() {
return descriptor.version().toString();
}

@Override
public ModuleDescriptor descriptor() {
return descriptor;
return mcv.version;
}
}
40 changes: 36 additions & 4 deletions src/main/java/cpw/mods/jarhandling/impl/SimpleJarMetadata.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
package cpw.mods.jarhandling.impl;

import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.LazyJarMetadata;
import cpw.mods.jarhandling.SecureJar;
import org.jetbrains.annotations.Nullable;

import java.lang.module.ModuleDescriptor;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;

/**
* {@link JarMetadata} implementation for a non-modular jar, turning it into an automatic module.
*/
public record SimpleJarMetadata(String name, String version, Set<String> pkgs, List<SecureJar.Provider> providers) implements JarMetadata {
public class SimpleJarMetadata extends LazyJarMetadata implements JarMetadata {
private final String name;
private final String version;
private final Supplier<Set<String>> packagesSupplier;
private final List<SecureJar.Provider> providers;

public SimpleJarMetadata(String name, String version, Supplier<Set<String>> packagesSupplier, List<SecureJar.Provider> providers) {
this.name = name;
this.version = version;
this.packagesSupplier = packagesSupplier;
this.providers = providers.stream().filter(p -> !p.providers().isEmpty()).toList();
}

@Override
public String name() {
return name;
}

@Override
public ModuleDescriptor descriptor() {
@Nullable
public String version() {
return version;
}

@Override
public ModuleDescriptor computeDescriptor() {
var bld = ModuleDescriptor.newAutomaticModule(name());
if (version()!=null)
bld.version(version());
bld.packages(pkgs());
providers.stream().filter(p->!p.providers().isEmpty()).forEach(p->bld.provides(p.serviceName(), p.providers()));
bld.packages(packagesSupplier.get());
providers.forEach(p->bld.provides(p.serviceName(), p.providers()));
return bld.build();
}

@Override
public List<SecureJar.Provider> providers() {
return Collections.unmodifiableList(providers);
}
}