Thursday, February 25, 2016

Reading MANIFEST.MF Values From a JAR File on your Class Path

Abstract
Recently, I was looking at building a simple "about" page for my newest application (See project Riviera, a database versioning source code management tool).  I started to build a properties file Maven would filter values into, but then I realized it was not necessary.  The JAR MANIFEST.MF file had almost all the metadata already.  So all I had to do was read the properties from MANIFEST.MF, which I found more difficult than I thought it should be. Here's my solution.

Code
Listing 1 shows the code.

Listing 1: Code to read MANIFEST.MF from JAR file on the class path

Attributes attributes;
try {
    URL jarURL 
        = this.getClass().getProtectionDomain().getCodeSource().getLocation();
    
    URI manifestUri 
        = new URI(String.format("jar:%s!/%s", jarURL, JarFile.MANIFEST_NAME));
    
    InputStream is 
        = manifestUri.toURL().openStream()
    
    Manifest manifest 
        = new Manifest(is);
    
    attributes 
        = manifest.getMainAttributes();            

    is.close();
} catch (Exception e) {
    attributes = new Attributes();
}

Let's take a look at this code in a bit more detail.

The URI object is the key to being able to read the MANIFEST.MF file from a JAR.  To read the contents of a JAR file, a properly formatted URI string looks like this:

"jar:file:/C:/path/to/file.jar!/META-INF/MANIFEST.MF"

The jar:file and ! parts are required in the URI.  The rest of the URI depends on the specific location of the JAR file on your file system and what file in the JAR you want to stream.  Knowing what file you want to read is a given, but how do you get the fully-qualified location of the JAR file?

It stands to reason that if the JVM is executing code from a class it got from a JAR file then the JVM should be able to tell you where that JAR file is located. After all, the JVM needs to know where the JAR file is in order to load the class!  Turns out its simple to find this information.  You get the fully-qualified location of the JAR file from a class's ProtectionDomain that comes from the JAR file.  Once you have the fully-qualified location, you can build a properly formatted URI string.  Then once you have a URI object, then the magic happens.

From the URI object you can get a URL object by using URI#toUrl().  Then after you have a URL object you get an InputStream to the resource using the URL#openStream() method. Under the covers, the URL object determines it needs a JarURLConnection to open the stream.

Once you have the InputStream, pass it along to the Manifest constructor and you're done!  Use Manifest#getMainAttributes() to get the Attributes and then use Attributes#getValue(key) to get the value you want.

Let's it!  Now you can read the the name/value attributes from the MANIFEST.MF file.

Enjoy!

References
Bozho. (2010, February 16). How to read a file from a jar file?. stackoverflow.com. Retrieved January 27, 2016 from http://stackoverflow.com/questions/2271926/how-to-read-a-file-from-a-jar-file.