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

Support Multi Release Jars #209

Merged
merged 4 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.vafer</groupId>
<artifactId>jdependency</artifactId>
<version>2.9.0</version>
<version>2.9.1-SNAPSHOT</version>
<name>jdependency</name>
<description>This project provides an API to analyse class dependencies</description>
<url>http://github.com/tcurdt/jdependency</url>
Expand All @@ -41,6 +41,12 @@
<email>krosenvold@apache.org</email>
</developer>
</developers>
<contributors>
<contributor>
<name>Niels Basjes</name>
<email>nielsbasjes@apache.org</email>
</contributor>
</contributors>
<scm>
<connection>scm:git:git@github.com:tcurdt/jdependency.git</connection>
<developerConnection>scm:git:git@github.com:tcurdt/jdependency.git</developerConnection>
Expand Down
113 changes: 112 additions & 1 deletion src/main/java/org/vafer/jdependency/Clazz.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.commons.io.FilenameUtils.separatorsToUnix;

/**
* A `Clazz` represents the single class identifier inside a classpath.
Expand All @@ -32,16 +36,123 @@ public final class Clazz implements Comparable<Clazz> {
private final Set<Clazz> references = new HashSet<>();
private final Map<ClazzpathUnit, String> units = new HashMap<>();

public static final class ClazzFile {
private ClazzpathUnit unit;
private String filename;

public ClazzFile(ClazzpathUnit unit, String filename) {
this.unit = unit;
this.filename = filename;
}

public ClazzpathUnit getUnit() {
return unit;
}

public String getFilename() {
return filename;
}

@Override
public String toString() {
return "ClazzFile{" +
"unit=" + unit +
", filename='" + filename + '\'' +
'}';
}
}

// 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, ClazzFile> classFilenames = new HashMap<>();

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

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

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

public static final class ParsedFileName {
public String className;
public String forJava;

@Override
public String toString() {
return "ParsedFileName{" +
"className='" + className + '\'' +
", forJava='" + forJava + '\'' +
'}';
}
}

/**
* Determine the class name for the provided filename.
*
* @param pFileName The filename
* @return the class name for the provided filename OR null if it is not a .class file.
*/
public static ParsedFileName parseClassFileName(String pFileName) {
if (pFileName == null || !pFileName.endsWith(".class")) {
return null;// Not a class filename
}
// foo/bar/Foo.class -> // foo.bar.Foo

Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName);
if (!matcher.matches()) {
return null;
}
ParsedFileName result = new ParsedFileName();
result.forJava = matcher.group(1);
result.className = separatorsToUnix(matcher.group(2)).replace('/', '.');

if (result.forJava == null || result.forJava.isEmpty()) {
if (result.className.contains("-")) {
return null;
}
result.forJava = "8";
}

return result;
}

/**
* Determine if the provided filename is the name of a class that is specific for a java version.
* @param pFileName The filename to be evaluated
* @return true if this is a filename for a specific java version, false if it is not
*/
public static boolean isMultiReleaseClassFile(String pFileName) {
if (pFileName == null) {
return false;
}
Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName);
if (!matcher.matches()) {
return false;
}
return matcher.group(1) != null && !matcher.group(1).isEmpty();
}

/**
* Record that this class name can be found at:
* @param pUnit The unit in which the class can be found
* @param pForJava For which Java version
* @param pFileName Under which filename in the jar.
*/
public void addMultiReleaseFile(ClazzpathUnit pUnit, String pForJava, String pFileName) {
classFilenames.put(pForJava, new ClazzFile(pUnit, pFileName));
}

public String getName() {
return name;
}

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

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

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

}
38 changes: 22 additions & 16 deletions src/main/java/org/vafer/jdependency/Clazzpath.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@
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 org.apache.commons.io.input.MessageDigestInputStream;
import org.objectweb.asm.ClassReader;
import org.apache.commons.io.input.MessageDigestCalculatingInputStream;
import static org.apache.commons.io.FilenameUtils.normalize;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;

import org.vafer.jdependency.Clazz.ParsedFileName;
import org.vafer.jdependency.asm.DependenciesClassAdapter;

import static org.vafer.jdependency.Clazz.parseClassFileName;
import static org.vafer.jdependency.utils.StreamUtils.asStream;


Expand All @@ -46,20 +50,16 @@ public final class Clazzpath {
private final boolean versions;

private abstract static class Resource {
public final String fileName;
public final String forJava;
public final String name; // Class name !

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

public final String name;

Resource( final String pName ) {
Resource( final String pFileName ) {
super();

final int all = pName.length();

// foo/bar/Foo.class -> // foo.bar.Foo
this.name = separatorsToUnix(pName)
.substring(0, all - ext)
.replace('/', '.');
this.fileName = pFileName;
ParsedFileName parsedFileName = parseClassFileName(pFileName);
tcurdt marked this conversation as resolved.
Show resolved Hide resolved
forJava = parsedFileName.forJava;
name = parsedFileName.className;
}

abstract InputStream getInputStream() throws IOException;
Expand All @@ -68,7 +68,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 All @@ -86,7 +86,7 @@ public boolean removeClazzpathUnit( final ClazzpathUnit pUnit ) {
for (Clazz clazz : unitClazzes) {
clazz.removeClazzpathUnit(pUnit);
if (clazz.getClazzpathUnits().size() == 0) {
clazzes.remove(clazz.toString());
clazzes.remove(clazz.getName());
tcurdt marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -168,7 +168,8 @@ private ClazzpathUnit addClazzpathUnit( final Iterable<Resource> resources, fina
InputStream inputStream = resource.getInputStream();
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final MessageDigestCalculatingInputStream calculatingInputStream = new MessageDigestCalculatingInputStream(inputStream, digest);
final MessageDigestInputStream calculatingInputStream =
MessageDigestInputStream.builder().setInputStream(inputStream).setMessageDigest(digest).get();

if (versions) {
inputStream = calculatingInputStream;
Expand All @@ -190,6 +191,7 @@ private ClazzpathUnit addClazzpathUnit( final Iterable<Resource> resources, fina
clazz = new Clazz(clazzName);
}
}
clazz.addMultiReleaseFile(unit, resource.forJava, resource.fileName);
final String d = Base64.getEncoder().encodeToString(digest.digest());
clazz.addClazzpathUnit(unit, d);

Expand Down Expand Up @@ -243,6 +245,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
Loading