Skip to content

Commit

Permalink
Basic support for handling Multi Release Jars
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsbasjes committed Nov 6, 2023
1 parent 0c4fa84 commit 3572d22
Show file tree
Hide file tree
Showing 24 changed files with 750 additions and 18 deletions.
16 changes: 15 additions & 1 deletion src/main/java/org/vafer/jdependency/Clazz.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,30 @@ public final class Clazz implements Comparable<Clazz> {
private final Set<Clazz> references = new HashSet<>();
private final Map<ClazzpathUnit, String> units = new HashMap<>();

// Usually a class is only in a single file.
// When using MultiRelease Jar files this can be multiple files, one for each java release specified.
// The default filename is under the key "8".
private final Map<String, String> classFilenames = new HashMap<>();

// The name of the class (like "org.vafer.jdependency.Clazz")
private final String name;

public Clazz( final String pName ) {
name = pName;
}

public void addMultiReleaseFile(String pForJava, String pFileName) {
classFilenames.put(pForJava, pFileName);
}

public String getName() {
return name;
}

public Map<String, String> getFileNames() {
return classFilenames;
}

public void addClazzpathUnit( final ClazzpathUnit pUnit, final String pDigest ) {
units.put(pUnit, pDigest);
}
Expand Down Expand Up @@ -116,7 +130,7 @@ public int compareTo( final Clazz pO ) {
}

public String toString() {
return name;
return name + " in " + classFilenames;
}

}
38 changes: 30 additions & 8 deletions src/main/java/org/vafer/jdependency/Clazzpath.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
import java.util.Map;
import java.util.Set;
import java.util.Base64;
import java.util.TreeMap;
import java.util.jar.JarInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.objectweb.asm.ClassReader;
import org.apache.commons.io.input.MessageDigestCalculatingInputStream;
Expand All @@ -45,21 +48,35 @@ public final class Clazzpath {
private final Map<String, Clazz> clazzes = new HashMap<>();
private final boolean versions;

private static final Pattern EXTRACT_MULTI_RELEASE_JAVA_VERSION = Pattern.compile("META-INF[/\\\\]versions[/\\\\](\\d+)[/\\\\]([^.]+).class$");

private abstract static class Resource {

private static final int ext = ".class".length();

public final String fileName;
public final String forJava;
public final String name;

Resource( final String pName ) {
Resource( final String pFileName ) {
super();
this.fileName = pFileName;

final int all = pFileName.length();

Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName);
if (matcher.find()) {
forJava = matcher.group(1);
this.name = separatorsToUnix(matcher.group(2))
.replace('/', '.');
} else {
forJava = "8";
// foo/bar/Foo.class -> // foo.bar.Foo
this.name = separatorsToUnix(pFileName)
.substring(0, all - ext)
.replace('/', '.');
}

final int all = pName.length();

// foo/bar/Foo.class -> // foo.bar.Foo
this.name = separatorsToUnix(pName)
.substring(0, all - ext)
.replace('/', '.');
}

abstract InputStream getInputStream() throws IOException;
Expand All @@ -68,7 +85,7 @@ private abstract static class Resource {
private static boolean isValidResourceName( final String pName ) {
return pName != null
&& pName.endsWith(".class")
&& !pName.contains( "-" );
&& ( !pName.contains( "-" ) || pName.contains("META-INF/versions/") );
}

public Clazzpath() {
Expand Down Expand Up @@ -190,6 +207,7 @@ private ClazzpathUnit addClazzpathUnit( final Iterable<Resource> resources, fina
clazz = new Clazz(clazzName);
}
}
clazz.addMultiReleaseFile(resource.forJava, resource.fileName);
final String d = Base64.getEncoder().encodeToString(digest.digest());
clazz.addClazzpathUnit(unit, d);

Expand Down Expand Up @@ -243,6 +261,10 @@ public Set<Clazz> getClazzes() {
return new HashSet<>(clazzes.values());
}

public Map<String, Clazz> getClazzesMap() {
return new TreeMap<>(clazzes);
}

public Set<Clazz> getClashedClazzes() {
final Set<Clazz> all = new HashSet<>();
for (Clazz clazz : clazzes.values()) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/vafer/jdependency/ClazzpathUnit.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public final class ClazzpathUnit {

Expand All @@ -36,6 +37,10 @@ public Set<Clazz> getClazzes() {
return new HashSet<>(clazzes.values());
}

public Map<String, Clazz> getClazzesMap() {
return new TreeMap<>(clazzes);
}

public Clazz getClazz( final String pClazzName ) {
return clazzes.get(pClazzName);
}
Expand Down
84 changes: 75 additions & 9 deletions src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,38 @@

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;

import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class ClazzpathUnitTestCase {

private static Path resourcePath( String filename ) {
return Paths.get(filename);
ClassLoader classLoader = ClazzpathUnitTestCase.class.getClassLoader();
URL resource = classLoader.getResource(filename);
if (resource == null) {
return null;
}
return Paths.get(resource.getFile());
}

private static File resourceFile( String filename ) {
return Paths.get(filename).toFile();
ClassLoader classLoader = ClazzpathUnitTestCase.class.getClassLoader();
URL resource = classLoader.getResource(filename);
if (resource == null) {
return null;
}
return new File(resource.getFile());
}

@Test
Expand Down Expand Up @@ -69,10 +83,8 @@ public void testIssue47() throws IOException {
.flatMap( i -> i.getClazzpathUnits().stream() )
.map( i -> i.toString() )
.collect(Collectors.toSet());
final Set<String> unitse = new HashSet<String>(Arrays.asList(
"woodstox-core-6.2.3.jar"
));
assertEquals(unitse, units);
assertEquals(1, units.size());
assertTrue(units.iterator().next().endsWith("woodstox-core-6.2.3.jar"));
}

@Test
Expand Down Expand Up @@ -153,13 +165,13 @@ public void testShouldHaveUnitId() throws IOException {
final Clazzpath cp = new Clazzpath();

final ClazzpathUnit u1 = cp.addClazzpathUnit(resourceFile("jar1.jar"));
assertEquals(u1.toString(), "jar1.jar");
assertTrue(u1.toString().endsWith("jar1.jar"));

final ClazzpathUnit u1e = cp.addClazzpathUnit(resourceFile("jar1.jar"), "jar1");
assertEquals(u1e.toString(), "jar1");

final ClazzpathUnit u2 = cp.addClazzpathUnit(resourcePath("jar2.jar"));
assertEquals(u2.toString(), "jar2.jar");
assertTrue(u2.toString().endsWith("jar2.jar"));

final ClazzpathUnit u2e = cp.addClazzpathUnit(resourcePath("jar2.jar"), "jar2");
assertEquals(u2e.toString(), "jar2");
Expand All @@ -179,4 +191,58 @@ public void testDependencies() throws IOException {

}

private void verifyFileInClazz(Clazz clazz, String expectedFilename, String forJava, boolean mustBePresent) {
String actualFileName = clazz.getFileNames().get(forJava);
if (mustBePresent) {
assertEquals("Incorrect filename for Java "+forJava, expectedFilename, actualFileName);
} else {
assertNull("Unexpected filename for Java "+forJava, actualFileName);
}
}

private void verifyClazzFiles(Clazz clazz, boolean have8, boolean have11, boolean have17) {
String expectedFilename = clazz.getName().replace(".", "/")+".class";
verifyFileInClazz(clazz, expectedFilename, "8", have8);
verifyFileInClazz(clazz, "META-INF/versions/11/" + expectedFilename, "11", have11);
verifyFileInClazz(clazz, "META-INF/versions/17/" + expectedFilename, "17", have17);
}

@Test
public void testMultiReleaseJar() throws IOException {
final Clazzpath cp = new Clazzpath();

// The Application
final ClazzpathUnit app = cp.addClazzpathUnit(resourceFile("uses-multi-jdk-1.0.jar"));
Map<String, Clazz> appClazzes = app.getClazzesMap();

assertEquals(1, appClazzes.size());
verifyClazzFiles(appClazzes.get("nl.example.Main"), true, false, false);

// The multi release dependency
final ClazzpathUnit dependency = cp.addClazzpathUnit(resourceFile("multi-jdk-1.0.0.jar"));
Map<String, Clazz> dependencyClazzes = dependency.getClazzesMap();
// Java 8, Java 11, Java 17
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.Main"), true, false, false);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.App"), true, true, true);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.AbstractJavaVersion"), true, false, false);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.JavaVersion"), true, true, false);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.Unused"), true, true, true);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.OnlyUsedInJava17"), false, true, false);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.SpecificToJava11"), false, true, false);
verifyClazzFiles(dependencyClazzes.get("nl.basjes.maven.multijdk.SpecificToJava17"), false, false, true);

// Check which are obsolete
final Set<Clazz> removable = cp.getClazzes();
removable.removeAll(appClazzes.values());
removable.removeAll(app.getTransitiveDependencies());

Map<String, Clazz> removableClazzes = removable
.stream()
.collect(Collectors.toMap(Clazz::getName, Function.identity()));

assertEquals(2, removableClazzes.size());
assertTrue(removableClazzes.containsKey("nl.basjes.maven.multijdk.Main"));
assertTrue(removableClazzes.containsKey("nl.basjes.maven.multijdk.Unused"));
}

}
Loading

0 comments on commit 3572d22

Please sign in to comment.