Abstract: Packages should have versions numbers embedded in the Jar file's manifest. Our guest author Herman Lintvelt shows us how.
Welcome to the 26th issue of The Java(tm) Specialists' Newsletter. My silence these past two weeks was due to having my hands full moving my outlook contacts list to the new mailserver. Yes, it has finally happened! In addition, I've done some cosmetic changes to the archive of newsletters and as you can see, the newsletter is now sent out in HTML format with syntax highlighted code. I want to thank Peter Carruthers for letting me use some ideas from his newsletter for this new format.
I am very grateful to fellow Java guru Herman Lintvelt for authoring this week's newsletter. He is the only person I know who understands the javax.swing.JTree, perhaps I should call him Dr. Swing. In case you need some serious Java GUI development done, please send him an email, or if you have GUI code written by amateurs that needs to be cleaned up.
Herman did his Bachelor of Science Degree in Computer Science in 1998 at "University" of Stellenbosch, and since then has worked almost exclusively in Java, especially using Swing and JDBC, with bits of JAI and JMF. After only 2 years as sucker^H^H^H^H^H^Hemployee, he decided it's not for him and recently started Polymorph Systems to enter the "freelance contractor" market.
Please remember to forward this newsletter to as many Java enthusiasts as you know who might be interested in receiving such a newsletter. Special thanks to the person who sent the last newsletter to their local JUG, it caused about 30 subscriptions :-).
And now for Herman's first newsletter...
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
While having a (rare) bit of idle time in this coldest, wettest winter of probably the last decade down here at the Southern tip of Africa, I thought it might be good to write about package versioning. Talking of wettest, I live on a wine-farm near Stellenbosch, and were it not for my trusty old '76 Jeep, I would have been cut off from civilization last week, as the dirt road was turned into a muddy river, but this is not a 4x4 newsletter, so back to Java(tm)...
Versioning of packages is a very good discipline to follow. Yes, if you have source-control package like SourceSafe, CVS, Clearcase, etc, you can (probably) be rest assured that you can keep track of your sourcecode versions. But what happens if your jar-files are finally (after months of intensive testing by expert testers ;-) distributed to the client and the totally unexpected happens: a bug shows up! You need to be able to determine whether the client had an old version of the package, as the newest version definitely fixed all the remaining bugs.
I like simple things (that's why this newsletter is not complex), and with package versioning it must be the same: it must be easy and fast to keep the package version information up to date (else I will be too tempted to ignore the versioning process). In a project I worked on previously, we added a class to each java package which implemented the same interface and returned the major, minor, patch and build numbers as hard-coded strings. Added to this was a "PackageVersionInterrogator" class that would then get these strings from all the packages when the debug logger required them. This had a few disadvantages:
But Alas!!, once again our friends at Sun had a lot of foresight. A while back I
"discovered" the existence of the java.lang.Package
class, and what's more: this class
contains all kinds of nice accessor methods for versioning information! How do you
use it?
Easy, for every jar file in your project (where every jar file can contain one or
more java
packages), you need to create your own manifest.mf
file (or whatever name you give to it),
and invoke the option of the jar-tool to use this manifest file.
Let's look at how this manifest file will look. Suppose we have some classes in a
package called
com.worldsgreatestApps.java.media
and some in a package called
com.worldsgreatestApps.java.media.ui
. When we run the jar-tool to create a jar-file
for these classes, we use the following manifest.mf
file (which will be put in
the \META-INF subdir inside the jar file):
Manifest-Version: 1.0 Main-Class: jmftest.PackageTest Specification-Title: Java Media Framework Specification-Version: 2.0 Specification-Vendor: Sun Name: com/worldsgreatestApps/java/media/ Implementation-Title: WorldsGreatestApps Super Media Framework Implementation-Version: 1.0.2 Implementation-Vendor: Polymorph Systems Name: com/worldsgreatestApps/java/media/ui Specification-Title: Java Media Framework Specification-Version: 2.0 Specification-Vendor: Sun Implementation-Title: WorldsGreatestApps Super Media Framework UI classes Implementation-Version: 1.1.1 Implementation-Vendor: Polymorph Systems
Some observations:
Main-Class
is not part of this versioning, it
indicates the main class to run in the jar file, and thus enables
you to "run" this jar file directly by using
java -jar jarfilename.jar
.Specification-[Title/Version/Vendor]
specifies information about the
specification implemented inside the package(s)Implementation-[Title/Version/Vendor]
specifies information about the
specific implementation contained in the package.Name
(and the empty line in front of it) specifies the name of a package,
and must preceed the section for every package in the manifest file. Thus all packages
in
the jar-file for which we need versioning info (should be all the packages) will have
a
section in the manifest file.Specification
and Implementation
entries can be
specified in the "general" section of the manifest file (like the first Specification
entries in the example), and these entries will then be used to describe packages
that do not have specific entries in the manifest file.X-Version
entry values are -by convention- in the form of
major.minor.micro(/patch), but it can basically be any descriptive string.
How do I use this information in my java code? Well, if the class loader finds package
information like this for the package that a class belongs to, it will create a
java.lang.Package
object for that class, which you can get a handle to by
calling on your classinstance.getClass().getPackage()
. This object has some
methods to get all the info specified in the manifest file.
If no information is specified for a specific package, the classloader first looks
to see
if there is a "general section" for the version information (i.e. Specification
and Implementation
entries at the top of the file, before the first empty line).
If it is there, it will use this info to populate the Package
object; else
it will create a Package object that will return null
on the version-info method calls.
What's the advantages of using this method to do your package versioning? Well,
it's simple and easy to use. Your debug logger can use the standard Java(tm) API to get
package version information, and can get a handle on all the loaded packages by
calling Package.getPackages()
, for example:
/** simple class to list all loaded packages with */ public class PackageLister { //... public static void listPackages() { Package[] allPackages = Package.getPackages(); System.out.println("All loaded packages:"); for(int i=0; i < allPackages.length; i++) { System.out.println("" + (i+1) + ": " + allPackages[i].getName() + ": " + allPackages[i].getImplementationTitle() + ", version: " + allPackages[i].getImplementationVersion()); } } //... }
Another big advantage: on the client site it's easy to open the jar file and view the manifest file manually if (by some operating system or network error of course) your application won't even start up enough to produce debug output.
I hope this inspired those *few* of you who are not using package versioning techniques to start doing it, and those who are using overly complex techniques or tools, to simplify. I'm very interested to know what versioning "systems" are being used out there, so please feel free to email me at herman@polymorph.co.za with your ideas and descriptions of the systems you are using for versioning your projects.
Vriendelike groete
Herman
P.S. In case you cannot get this working, make sure that you have at least one class from the specified package loaded by the classloader at the time of the call to Package.getPackage() or that call will return null. Thanks to Victor Grazi for pointing that out!
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.