Home > database >  How do I get the jmod file of a given JDK class and walk tree of jmod?
How do I get the jmod file of a given JDK class and walk tree of jmod?

Time:11-10

Up to JDK8, I could use the following to iterate on rt.jar classes. Given a single class, I could find all others like this :

final URL location = clazz.getProtectionDomain().getCodeSource().getLocation();
final File file = new File(location.toURI());
try (JarFile jarFile = new JarFile(file)) {
    final Enumeration<JarEntry> entries = jarFile.entries();
    while (entries.hasMoreElements()) {
        final JarEntry jarEntry = entries.nextElement();
        // do something...
    }
}

After JDK8, using this clazz.getProtectionDomain().getCodeSource().getLocation() isn't valid anymore:

java.lang.NullPointerException: Cannot invoke "java.security.CodeSource.getLocation()" because the return value of "java.security.ProtectionDomain.getCodeSource()" is null

Is there good replacement for this ? I'm thinking of doing a special case like this:

if (clazz.getProtectionDomain().getCodeSource() == null) {
  // find URL to the jmod ...
}

However a solution that will work in both cases would be preferable.

CodePudding user response:

There’s a wrong assumption in your question, as classes are never loaded from a .jmod file.

You can get the location of a module like

Module m = clazz.getModule();
System.out.println(m.getLayer().configuration()
    .findModule(m.getName()).flatMap(rm -> rm.reference().location())
    .orElse(null));

which doesn’t work for the “unnamed module”, read classes loaded through the classpath, so you would have to resort to .getProtectionDomain().getCodeSource().getLocation() if m.isNamed() is false.

However, for the built-in modules, the URI will always be jrt:/module-name, so to iterate over the platform classes, you don’t need this at all.

For example, this code snippet lists all classes in the java.lang package:

try(var list = Files.list(Paths.get(URI.create("jrt:/java.base/java/lang")))) {
         list.map(p -> p.getFileName().toString())
             .filter(s -> s.endsWith(".class"))
             .map(s -> "java.lang."   s.substring(0, s.length() - 6))
             .forEach(System.out::println);
}

and to get the class file bytes for java.lang.Object, you can simply use

byte[] objectClassFile = Files.readAllBytes(
    Paths.get(URI.create("jrt:/java.base/java/lang/Object.class")));
  • Related