2

Essentially I'm asking something similar to this: How do I get a list of Java class dependencies for a main class? but I'd like to do it using a Java API. I'd prefer not to exec jdeps and scrape the output.

Extra points if it uses https://github.com/classgraph/classgraph since our project already uses that library and it is awesome for scanning classes without actually instantiating everything it processes.

Oh, and this would be restricted to working on JDK 8.

[Update]

Here is a bit more background. Let's say I have a bundled application. Perhaps a Spring Boot app or something that can be deployed to a cloud platform like Cloud Foundry or Heroku. The app probably contains framework jars (like Spring), utility jars (logging, templating, json/xml processing, etc.), application domain classes and then all necessary transitive dependencies. Assume this is all either bundled into an uber-jar or referenced via a classpath.

My use case would be: Given a class, and the uber-jar/classpath, what is the subset of jars (out of that uber-jar) that my given class requires as dependencies to instantiate it?

Jens D
  • 4,229
  • 3
  • 16
  • 19
  • The question isn't very specific. You mean dependencies one level deep? Iirc, Apache libraries include class analysis that should make an approximate one-level deep problem fairly simple. Building a full graph is harder. In fact since classes can be loaded by runtime calculations, a perfect solution for all possible classes is impossible. – Gene Oct 28 '19 at 23:06

1 Answers1

3

Turns out that I can do what I want with classgraph. The key is to set enableInterClassDependencies. Given a class, the following will process all dependencies, of that class, and determine which jars contain the relevant dependent classes:

import java.net.URI;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;

public class DependencyFinder {

  private final String clazz;

  public DependencyFinder(String clazz) {
    this.clazz = clazz;
  }

  public Set<URI> process() {
    ScanResult scanResult = new ClassGraph()
        .whitelistPackages()
        .enableInterClassDependencies()
        .scan();

    ClassInfo rootClass = scanResult.getClassInfo(clazz);
    Map<ClassInfo, ClassInfoList> dependencyMap = scanResult.getClassDependencyMap();

    Set<URI> results = new HashSet<>();
    Set<ClassInfo> seen = new HashSet<>();

    accumulateJars(new HashSet<>(dependencyMap.get(rootClass)), dependencyMap, results, seen);

    return results;
  }

  private void accumulateJars(Set<ClassInfo> roots, Map<ClassInfo, ClassInfoList> dependencies, Set<URI> accumulated, Set<ClassInfo> seen) {
    Set<ClassInfo> nextRoots = new HashSet<>();

    for (ClassInfo info : roots) {
      if (seen.contains(info)) {
        continue;
      }

      accumulated.add(info.getClasspathElementURI());
      seen.add(info);

      nextRoots.addAll(dependencies.get(info));
    }

    if (nextRoots.size() > 0) {
      accumulateJars(nextRoots, dependencies, accumulated, seen);
    }
  }

}
Jens D
  • 4,229
  • 3
  • 16
  • 19