3

I have a javaagent jar that I put on the bootclasspath using

Boot-Class-Path: myagent.jar

inside the MANIFEST.MF file.

I need to find out the directory on the filesystem in which the jar is located.

However the method described for this here doesnt seem to work for me:

 new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().g­etPath());

In this case the ProtectionDomain.getCodeSource() returns null. I guess this is happening because the jar has been put on the boot classpath. Because of this I also cannot do MyClass.getClassLoader() to get the resource location.

I am using Java 6.

Can anyone tell how to get the location of the jar?

Community
  • 1
  • 1
pdeva
  • 43,605
  • 46
  • 133
  • 171
  • I know that comments just saying "thanks" are frowned upon here, but just because I saw you mention `Boot-Class-Path`, I got curious and the information gathered helped me to elegantly solve the problem with agent bootstrap injection. I did the latter manually with lots of boilerplate code, now it just works as long as the user does not modify the artifact name before using it as an agent JAR. So **thank you!** – kriegaex Jun 30 '20 at 14:16

2 Answers2

9

You can use the System class loader to find classes on the boot class path. For example, this

System.out.println(
  ClassLoader.getSystemClassLoader().getResource("java/lang/String.class")
);

Will print out something like,

jar:file:/C:/Program%20Files/Java/jdk1.6.0_22/jre/lib/rt.jar!/java/lang/String.class

To find the location of MyClass.class on disk, do

String urlString = ClassLoader
 .getSystemClassLoader()
 .getResource("com/my/package/MyClass.class")
 .toString();

urlString = urlString.substring(urlString.indexOf("file:"), urlString.indexOf('!'));
URL url = new URL(urlString);
File file = new File(url.toURI());
System.out.println(file);
System.out.println(file.exists());

Update 2020-06-29 by kriegaex: Rather than writing a new answer to this old question, I want to enhance or update this very good answer, also taking account paths with spaces and Java 9+ modules.

Here is a method returning the class file path as a File instance. If a class file is part of a JAR or Java runtime module (URL starts with protocol jrt:), it just returns the paths to the JAR or to the JMOD file, which is something you might not want, but it is just a showcase you can edit to your heart's content. Of course this is still hacky, e.g. not considering classes loaded from a web URL instead of from file, but you get the idea.

public static File getFileForClass(String className) {
  className = className.replace('.', '/') + ".class";
  URL classURL = ClassLoader.getSystemClassLoader().getResource(className);
  if (classURL == null)
    return null;
  System.out.println("Class file URL: " + classURL);

  // Adapt this if you also have '.war' or other archive types
  if (classURL.toString().contains(".jar!")) {
    String jarFileName = classURL.getPath().replaceFirst("!.*", "");
    System.out.println("Containing JAR file: " + jarFileName);
    try {
      return new File(new URL(jarFileName).toURI());
    }
    catch (URISyntaxException | MalformedURLException e) {
      throw new RuntimeException(e);
    }
  }

  if (classURL.getProtocol().equals("jrt")) {
    String jrtModule = classURL.getFile().replaceFirst("/([^/]+).*", "$1");
    System.out.println("Target class is part of Java runtime module " + jrtModule);
    String jmodName = System.getProperty("java.home") + "/jmods/" + jrtModule + ".jmod";
    System.out.println("Containing Java module file: " + jmodName);
    return new File(jmodName);
  }
  try {
    return new File(classURL.toURI());
  }
  catch (URISyntaxException e) {
    throw new RuntimeException(e);
  }
}

When I call this from one of my IntelliJ IDEA projects with JDK 14 installed under a path containing spaces and deliberately adding a JAR also containing a path with spaces for testing, this code...

public static void main(String[] args) throws IOException {
  Instrumentation instrumentation = ByteBuddyAgent.install();
  instrumentation.appendToSystemClassLoaderSearch(
    new JarFile("C:/Program Files/JetBrains/IntelliJ IDEA 2018.3/lib/idea_rt.jar")
  );
  Stream
    .of(
      Weaver.class.getName(),
      String.class.getName(),
      ByteBuddy.class.getName(),
      "com.intellij.execution.TestDiscoveryListener"
    )
    .forEach(className -> System.out.printf("Found file: %s%n%n", getFileForClass(className)));
}

... yields this console output:

Class file URL: file:/C:/Users/alexa/Documents/java-src/Sarek/sarek-aspect/target/classes/dev/sarek/agent/aspect/Weaver.class
Found file: C:\Users\alexa\Documents\java-src\Sarek\sarek-aspect\target\classes\dev\sarek\agent\aspect\Weaver.class

Class file URL: jrt:/java.base/java/lang/String.class
Target class is part of Java runtime module java.base
Containing Java module file: C:\Program Files\Java\jdk-14.0.1/jmods/java.base.jmod
Found file: C:\Program Files\Java\jdk-14.0.1\jmods\java.base.jmod

Class file URL: jar:file:/C:/Users/alexa/.m2/repository/net/bytebuddy/byte-buddy/1.10.13/byte-buddy-1.10.13.jar!/net/bytebuddy/ByteBuddy.class
Containing JAR file: file:/C:/Users/alexa/.m2/repository/net/bytebuddy/byte-buddy/1.10.13/byte-buddy-1.10.13.jar
Found file: C:\Users\alexa\.m2\repository\net\bytebuddy\byte-buddy\1.10.13\byte-buddy-1.10.13.jar

Class file URL: jar:file:/C:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar!/com/intellij/execution/TestDiscoveryListener.class
Containing JAR file: file:/C:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar
Found file: C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\lib\idea_rt.jar
kriegaex
  • 63,017
  • 15
  • 111
  • 202
sbridges
  • 24,960
  • 4
  • 64
  • 71
  • doesnt work when the path has a space in it, eg jar:file:/C:/test%20recorder/recorder2/jar/recorder-1.0.0-beta-build86.jar!/com/chronon/sb/recorder/RecorderMain.class. In this case making a new File("C:/test%20recorder/recorder2/jar/recorder-1.0.0-beta-build86.jar").exists() returns false – pdeva Apr 23 '11 at 23:02
  • You just need to unescape the url, http://stackoverflow.com/questions/623861/how-do-you-unescape-urls-in-java – sbridges Apr 24 '11 at 01:53
  • in the url.substring() you did "file:".length() + 1, wont the +1, delete the begining '/' on a unix file system? – pdeva Apr 24 '11 at 08:54
  • possibly, it should be easy to fix though – sbridges Apr 24 '11 at 13:25
  • 1
    Note: URLDecoder must NOT be used to unescape the URL (it would convert + to spaces which is wrong). The proper way to deal with URLs is `url.toURI().getSchemeSpecificPart()` (it will return decoded url) – Vladimir Sitnikov Sep 09 '18 at 13:08
  • 1
    I took the liberty of adding more sample code to this very good answer, taking into account paths with spaces and Java 9+ modules. I did not want to write a new answer and steal reputation from this one. I hope the edit is okay - no offense intended. Otherwise @sbridges can just revert it. – kriegaex Jun 29 '20 at 03:56
0

You could do something like this:

final String   bootClassPath;
final String[] entries;

// different VM may use a different string here...
bootClassPath = System.getProperty("sun.boot.class.path");
entries       = bootClassPath.split(File.pathSeparator);

for(final String entry : entries)
{
    System.out.println(entry);
}

Which goes through each entry in the boot classpath. For each entry you could then get the JAR file and look at the manifest file and see if it is yours. You could add another entry in the manifest to look for if there isn't already something unique to find.

TofuBeer
  • 60,850
  • 18
  • 118
  • 163
  • RuntimeMXBean.getBootClassPath is probably better. – Brett Kail Apr 27 '11 at 06:22
  • Probably... been a long time since I cared about boot classpaths... I haven't kept up to date. The other answer is better than mine anyways (just keeping this one around in case it helps someone with something at some point in time :-) – TofuBeer Apr 27 '11 at 14:22