Monday, June 20, 2011

Accessing Package Version Information in Java Code

The Javadoc documentation for the Package class states:
Package objects contain version information about the implementation and specification of a Java package. This versioning information is retrieved and made available by the ClassLoader instance that loaded the class(es). Typically, it is stored in the manifest that is distributed with the classes.
In this blog post, I look at how Java code can use the Package class to access version information about a particular Java package's specification and implementation.

The Java Tutorials' Deployment Trail features the Packaging Programs in JAR Files lesson that includes the relevant section Setting Package Version Information. That short page demonstrates how to provide package version information inside a JAR's manifest file.

The Setting Package Version Information page indicates that seven pieces of information (seven lines in the manifest file) can be specified for package version information. The first line of the seven is the name of the package and is indicated by Name. The other six pieces of version information that can be specified are title, version, and vendor for the specification and the same title, version, and vendor for the implementation. An example of what this might look like is shown next in this example MANIFEST.MF file.

Example MANIFEST.MF File With Package Version Information
Name: dustin/examples/
Specification-Title: Dustin's Java/Groovy Examples
Specification-Version: 2011.06.17
Specification-Vendor: Marxian Software
Implementation-Title: Dustin Examples
Implementation-Version: 2011.06.17.02
Implementation-Vendor: Marxian Software

In the above example, the various version information is provided for the package dustin.examples because that (dustin/examples/) is listed after Name: .

These manifest entries can be created in an Ant build as shown in the next excerpt from an Ant build.xml file.

Excerpt from build.xml for Building JAR with Manifest with Package Version Information
<target name="jar"
        description="Package compiled classes into JAR file"
        depends="compile">
   <jar destfile="${dist.dir}/${jar.name}"
        basedir="${classes.dir}"
        filesonly="${jar.filesonly}">
     <manifest>
        <section name="common">
           <attribute name="Specification-Title" value="Example Java Classes" />
           <attribute name="Specification-Version" value="1.2" />
           <attribute name="Specification-Vendor" value="marxsoftware.blogspot.com" />
           <attribute name="Implementation-Version" value="1.2.1" />
           <attribute name="Implementation-Vendor" value="Dustin" />
           <attribute name="Implementation-Title" value="Java Packaging Example" />
        </section>
        <section name="dustin/examples/">
           <attribute name="Specification-Title" value="Dustin's Java/Groovy Examples (Spec)" />
           <attribute name="Specification-Version" value="2011.06.17" />
           <attribute name="Specification-Vendor" value="Marxian Software (Spec)" />
           <attribute name="Implementation-Title" value="Dustin Examples (Impl)" />
           <attribute name="Implementation-Version" value="2011.06.17.02" /> 
           <attribute name="Implementation-Vendor" value="Marxian Software (Impl)" />
           <attribute name="Package-Title" value="Dustin Examples (Package)" />
           <attribute name="Package-Version" value="2011.06.17.05" />
           <attribute name="Package-Vendor" value="Dustin (Package)" />
        </section>
     </manifest>
   </jar>
</target>

When the above Ant target is executed, it builds a JAR file with a MANIFEST.MF file that looks like that shown next.

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.0
Created-By: 1.7.0-ea-b134 (Oracle Corporation)

Name: common
Specification-Title: Example Java Classes
Specification-Version: 1.2
Specification-Vendor: marxsoftware.blogspot.com
Implementation-Version: 1.2.1
Implementation-Vendor: Dustin
Implementation-Title: Java Packaging Example

Name: dustin/examples/
Specification-Title: Dustin's Java/Groovy Examples (Spec)
Specification-Version: 2011.06.17
Specification-Vendor: Marxian Software (Spec)
Implementation-Title: Dustin Examples (Impl)
Implementation-Version: 2011.06.17.02
Implementation-Vendor: Marxian Software (Impl)
Package-Title: Dustin Examples (Package)
Package-Version: 2011.06.17.05
Package-Vendor: Dustin (Package)

The example shows a common section as well as a section specific to the dustin.examples package. Note that I specified Package-Title, Package-Version, and Package-Vendor in the Ant build file and they were included in the resultant manifest file. I'll return to these later in this post.

It is very easy to access the package information at this point. Use of Package.getPackages() to get all packages associated with the current class loader and Package.getPackage(String) to get a particular package by name from the current class loader are demonstrated in the next code listing for Main.java.

Main.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Simple class demonstrating in-code access of package-specific information as
 * specified in the Manifest file.
 */
public class Main
{
   /**
    * Display (to standard output) package details for provided Package.
    *
    * @param pkg Package whose details need to be printed to standard output.
    */
   private void displayPackageDetails(final Package pkg)
   {
      final String name = pkg.getName();
      out.println(name);
      out.println("\tSpec Title/Version: " + pkg.getSpecificationTitle() + " " + pkg.getSpecificationVersion());
      out.println("\tSpec Vendor: " +  pkg.getSpecificationVendor());
      out.println("\tImplementation: " + pkg.getImplementationTitle() + " " + pkg.getImplementationVersion());
      out.println("\tImplementation Vendor: " + pkg.getImplementationVendor());
   }

   /**
    * Display all packages associated with the class loader that do not start
    * with "sun", "com", "java", or "org".
    */
   private void displayCustomPackage()
   {
      final Package[] packages = Package.getPackages();
      for (final Package pkg : packages)
      {
         final String name = pkg.getName();
         if (   !name.startsWith("sun") && !name.startsWith("java")
             && !name.startsWith("com") && !name.startsWith("org") )
         {
            displayPackageDetails(pkg);
         }
      }
   }

   /**
    * Main executable that demonstrates use of Package.getPackages() and
    * Package.getPackage(String).
    *
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final Main me = new Main();
      me.displayCustomPackage();
      me.displayPackageDetails(Package.getPackage("dustin.examples"));
   }
}

The output from running the above is shown next.


The output shows that the package information described in the MANIFEST.MF's file is available to Java code. I intentionally included three attributes in the MANIFEST.MF example above that are not accessible from Java's Package class. These are Package-Title, Package-Version, and Package-Vendor. The Setting Package Version Information page links to the Package Version Specification, which has a section 1.5.8 JAR Manifest Format that shows use of these three attributes beginning with Package- rather than the three I showed earlier beginning with Implementation-. My example intentionally included both sets with different values for each to prove that its the three Implementation- attributes that are available via the Package class rather than the three Package- attributes.


Conclusion

I demonstrated in this blog post how to use Java's Package class to access version information about a particular Java package. To demonstrate this, I used Ant to specify the package version information in a JAR's MANIFEST.MF file as part of the javac task.

No comments: