by Vincent Dibartolo and Abraham Weintraub

Track class file versions

news
Aug 16, 200113 mins

Put your source control system to work

Few, if any, development environments dare cast off the convenience and safety of source control systems. Typically the more complex the environment, the more stringent the standards that govern the use of the system. When to check in files, when to compile, and when to move a jar file to the staging or live environments are decisions that don’t come lightly. But every once in a while a step is overlooked or the process goes awry, and you’re confused over which source file version is in the live environment. Or perhaps the customer requests the ability to check versions of various components from the command line. In such cases it’s necessary to interrogate the jar file directly.

Luckily the JDK provides flexibility when it comes to providing a solution to this problem. Using reflection, doclets, and jar file support available from within the running JVM, we propose two distinct solutions, each with pros and cons.

An important element common to both of these approaches is their reliance on the source control system. Most modern source control systems provide access to version information via a set of macros (sometimes called keywords) that are expanded by the source control system.

For instance, Perforce from Perforce Corp. provides access to version information through the RCS-like macro $Id$. So when the source file checks in, Perforce scans the file for that macro and expands the string to something like $Id: //depot/src/com/vdibart/util/VersionLister.java#1 $. Yes, those dollar signs are annoying, but you can easily parse them out; plus, they are necessary for the source control system to recognize the macro, especially after its first expansion.

It’s important to know your system’s macro capabilities, as each system expands the macros differently. SCCS, which is usually installed on Unix systems by default, only displays the version information when the file is not writeable (i.e. only when it’s checked in). On the other hand, PVCS shows the version information regardless of the source file’s state, and only updates the string when the file is checked in. Some systems also supply access to change notes and file ownership, which you could easily integrate with this article’s solutions. Other systems might only expand the macro when the file is first checked in and after that will ignore it. Again, check your source control system’s documentation for details.

With this versioning tool in your box, you can design some objects that utilize the macro expansion to return version information for compiled class files. We identify two approaches to implant this information in the jar file: The first has each class return its own version. The second lists the version information in the jar’s manifest file. We’ll cover each of these in turn.

The getVersion mechanism

Reflection takes center stage in the first approach. The concept is this: place a method, called getVersion(), in each unique class. The method should be public and static, but not final. The reasons for these requirements should be obvious; you want to be able to call the method from another object without instantiating an instance of the object supplying its version. The method shouldn’t be final because it is likely a subclass with a different version than its parent class, and therefore must have its own getVersion(). The getVersion() method should return a String object that contains a descriptive version message. How does it generate this version message? With the macro supplied by the version control system. Here’s a sample getVersion() method; notice how little code is needed:

/*
 * For RCS or Perforce systems
 */
public static String getVersion() {
      return "$Id$";
}//method getVersion

After macro expansion, you can imagine the method looks more like this:

/*
 * What it might look like expanded (assumes Perforce system).
 */
public static String getVersion() {
      return "$Id: //depot/common_core_technologies/crs/presentation_layer/scr/
      com/smallworld/util/VersionLister.java#1$";
}//method getVersion

Now you just need a way to iterate through the classes in the jar file and execute the getVersion() method. Reflection makes this task simple, letting you load and execute classes in the classpath by name. Strangely enough, Java’s ClassLoaderobject throws in a twist. The object locates and loads class files into the JVM. One system requirement is that you support the ability to get class file versions both inside a jar file (most likely retrieved from a command line application) and from classes currently loaded into a running virtual machine (retrieved by examining the class at runtime). But the ClassLoader optimizes class loading to reduce disk access and won’t load an already loaded class. This means that if one class version exists in memory and a different one exists on disk, the getVersion() method of the class in memory is the only one called. Therefore, we must enforce a couple rules:

  1. To support command line execution, our class, called VersionLister, must have a main() method that takes a jar filename and lists the versions of all classes within the jar that pass certain filtering criteria (based on package name). The main() method accesses a private method from the VersionLister class that recognizes that the jar file classes can be loaded directly. A typical command line execution might look like this:

    java com.vdibart.VersionLister -jarfile ./myjar.jar

    That would list the versions for all classes from mjar.jar, starting with the default package name (which is currently compiled into the class as com.vdibart).

  2. Next, we have a publicly accessible constructor for other classes to print out version information from within an already running JVM. This constructor accepts the path to a jar file, but in this case, only gets a list of class files within that jar and uses it as a reference list. It uses that reference list to execute the getVersion() method on classes loaded into memory.

The above two cases can ultimately be serviced by the same constructor, which is listed below:

private VersionLister(String lsJarFileName, boolean lbIsRefList) 
throws IOException, java.net.MalformedURLException, IllegalAccessException {
      URL jurl   = new URL("jar:file:" + lsJarFileName + "!/");
      JarURLConnection jconn = (JarURLConnection)jurl.openConnection();
      //if this is a reference list, use this VM's loader
      //to try to find the Class, otherwise a new URLClassLoader
      ClassLoader loader;
      if(lbIsRefList)
            loader = this.getClass().getClassLoader();
      else
            loader = URLClassLoader.newInstance( new URL[]{jurl} );
}//private constructor

If some of the classes above appear strange, we encourage you to read through the java.net package. The constructor creates a stream from which it can read the jar file and sets the ClassLoaderused to read the classes from that file. This is all setup code, of course, so don’t expect to see any heavy lifting here. Some lines of reflection-oriented code iterate over a set of classes, calling the getVersion() method on each one. Here are those lines; notice the use of the ClassLoaderobject:

Class thisClass  = loader.loadClass(lsThisName);
Method method    = thisClass.getMethod("getVersion", new Class[] {} );
String lsVersion = (String)method.invoke(null, new Object[]{});

The complete class is linked in the Resources section below. The code for VersionListeris specific to Perforce.

The doclet mechanism

You should be familiar with javadoc tags, like @version, which let you place instructive comments directly in the code. The javadoc executable then processes these tags, which create a nicely formatted HTML file containing the class documentation. Recent JDK releases have let developers leverage this technology through the concept of doclets, classes that adhere to the interfaces defined in com.sun.javadocand let you access the values of those tags for whatever purpose. Our purpose is to access the version information for a particular class via the @version javadoc tag. Although it is always good form to include the @versiontag at the top of your classes, it is particularly important to add it if you’re using this mechanism to list versions. Fortunately, it doesn’t take too much work. For instance, if you’re using PVCS, you must only do this:

/**
 * Class something that does something.
 * @author Vincent DiBartolo
 * @version $Revision$, $Date$
 */

The source control system then expands the above code to the following:

/**
 * Class something that does something.
 * @author Vincent DiBartolo
 * @version $Revision:   1.0  $, $Date:   20 Feb 2001 15:23:50  $
 */

The main difference between this mechanism and the getVersion()mechanism is that we don’t rely on reflection to do the work. This time we use a doclet to get the text of the @version tag so we can print it to the MANIFEST.MFfile. The text document serves as a table of contents for a jar file. This method has two major components: The first class is called ManifestCreator, and is actually a doclet. It extracts the version information from each class and writes the class name and version to the MANIFEST.MFfile. The second class, called ManifestFetcher, reads the MANIFEST.MF file that has been bundled with a particular jar file and searches for a class and its version, printing out the discovered information. Both classes are available from the Resources section below. As with most things Java, you only need a couple lines of code to do most of the work. Before we look at the code, let’s look at how the utilities work from the command line, giving you more of a feel for what they do.

The following code creates a MANIFEST.MF file, which you would include in a jar file using the jar executable. The -docletoption to the javadoc executable supplies the ManifestCreatorclass, which is the doclet described above. This class acts upon all the Java files in the directories specified by the next two parameters:

$>  javadoc -doclet com.traub.util.ManifestCreator H:/java/src/com/traub/bo/*.java H:/java/src/com/traub/po/*.java

Assuming the jar file containing the above MANIFEST.MFis /app/jdk1.2/jre/lib/ext/thejar.jar,

the first line below prints all class file versions and the second line prints the com.traub.util.MyDate version:

$> java com.traub.util.ManifestFetcher -fi /app/jdk1.2/jre/lib/ext/thejar.jar $> java com.traub.util.ManifestFetcher -class com.traub.util.MyDate

-fi /app/jdk1.2/jre/lib/ext/thejar.jar

To take advantage of the doclet interface, a class must implement the start(com.sun.javadoc.RootDoc) method. Here’s some code from the ManifestCreator.start(RootDoc)method that finds all the @version tags. This method is defined by the doclet specification, which magically makes this class a doclet:

public static boolean start(RootDoc loRoot){
...
      ClassDoc[]  loClassArray = loRoot.classes();
      // iterate through all of the classes 
      for(int ii = 0; ii < loClassArray.length; ++ii) {
            // get an array of all of the version tags for the current class
            Tag[] loTagArray = loClassArray[ii].tags("version");
            for(int jj = 0; jj < loTagArray.length; ++jj) {
                  //massage each tag in the array and write it to a stream
            }
      }
      return(true);
} // end of start

As you can see, there’s not a lot to it. The method specifies a com.sun.javadoc.RootDocobject, which gives an array of ClassDoc objects. Each element represents metadata about a particular class, including its tags, comments, and members. Therefore, the method ClassDoc.tags(String) returns an array of tags with that name from the current class. Of course we expect only one @version tag for each class, but we process in loop just in case something unexpected occurs. The massaging of the version tag is fairly dependent on the source control system you use, as each system formats the string differently. The methods that parse in the method ManifestCreator.extractVersion(String)(from the source code in Resources) are specific to PVCS. Here is some sample output from the ManifestCreatorclass, found in the MANIFEST.MFfile:

Name: com/traub/util/Logger.class
Implementation-Version: "1.22 , Apr 15 1999 14:06:26"

When it’s time to extract the version information, you use ManifestFetcher. This class first ensures that it’s running inside the correct JDK version (1.2 or higher). It then gets a reference to the manifest file:

java.util.jar.JarFile oJar       = new JarFile(lsJarName);
java.util.jar.Manifest oManifest = oJar.getManifest();

Only a couple more lines are required to get the entry from the manifest file that contains the version information:

Attributes loAttr = oManifest.getAttributes(<class name as a String object>);
String lsVersion  = loAttr.getValue("Implementation-Version");

The code above assumes that the entry is named "Implementation-Version", which you can certainly change. The source code contains a couple more goodies that we encourage you to look into.

Comparing both mechanisms

Each of the two methods has its strengths. The doclet mechanism introduces no overhead in terms of runtime processing or class size. Its contents are parsed out of the class before it’s compiled to bytecode, so this might be the best solution for some. Additionally, contrary to the getVersion() mechanism, the doclet lets you see the complete version list without doing more than unjarring the MANIFEST.MF file — a nice safety net for when problems arise.

The getVersion() mechanism introduces some overhead, and its inclusion in every class might become an eyesore. Regardless, we believe that overall the getVersion() mechanism is the safer of the two choices because it has less moving parts.

In some respects, the MANIFEST.MF file is an Achilles heel; if your development environment is unstable enough that you don’t know which class versions exist in the jar file, chances are you have the wrong version of MANIFEST.MF file in any given jar file. Given that the success of the doclet mechanism hinges on that file, you might be back to square one. The getVersion()mechanism, on the other hand, relies only on code that is compiled with the class, so there is little-to-no concern that the wrong version is returned.

Time savers

Regardless of which mechanism you prefer, it’s worth your time to develop a simple script to automate the insertion of relevant source control tags (with the

getVersion()

method if desired) into your current source code. Keep in mind that these source control tags can be a tremendous benefit if used simply in the class’s javadoc documentation, without being extracted to the jar file. This way you can tell how recently your documentation has been updated, which can help avoid other types of mistakes. In summary, your source control system combined with the JDK can provide some powerful time-saving features.

Vincent DiBartolo has worked with Java since its days as mostly a client-side solution. He has worked on everything from JDK 1.1 applets to batch applications using Java to server-side solutions like JSP/servlets and EJB. Always on the lookout for ways to improve the software development process, Vincent has tried his hand at most things Java and many things non-Java. Most recently he has been working extensively with XML-related technologies on the server and EJB containers such as Persistence PowerTier and BEA WebLogic. He received a BS degree in physics from the College of Mount St. Vincent and has worked in the defense, financial, and Internet industries. Abraham Weintraubis an independent consultant based in New York City. He is a Sun-certified programmer for the Java 2 Platform, specializing in server-side design and development for the financial services industry. Although Abe’s background is in C and FORTRAN, he has enjoyed working within the object-oriented paradigm and using design patterns over the last four years.