Wednesday, July 10, 2013

Comparing Manifest Files with Groovy

My last post discussed comparing the high-level contents of two JAR files using a Groovy script and a 2011 post covered displaying a particular JAR file's manifest file. This post illustrates the combination of those two ideas with an example of comparing the Manifest files of two different JAR files.

As illustrated in the just-referenced two blog posts, it is easy to manipulate the contents of a JAR file with Groovy. While this manipulation is all available in the standard JDK and is thus available to Java applications, Groovy is better suited for scripting needs like this. Groovy also provides some convenient syntax shortcuts. I like the fact that the Groovy script feels like a script rather than like an object-oriented production piece of software.

Here is the code for comparing the Manifest.MF files of two provided JAR files:

manifestDiff.groovy
#!/usr/bin/env groovy

/**
 * manifestDiff.groovy
 *
 * manifestDiff.groovy <first_jar_file> <second_jar_file>
 *
 * Script that compares the MANIFEST.MF files of two JAR files.
 */

if (args.length < 2)
{
   println "\nUSAGE: manifestDiff.groovy <first_jar_file> <second_jar_file>\n"
   System.exit(-1)
}

TOTAL_WIDTH = 180
COLUMN_WIDTH = TOTAL_WIDTH / 2 - 3
ROW_SEPARATOR = "-".multiply(TOTAL_WIDTH)

import java.util.jar.JarFile

def file1Name = args[0]
def jar1File = new JarFile(file1Name)
def num1Attrs = jar1File.manifest.mainAttributes.size()
def file2Name = args[1]
def jar2File = new JarFile(file2Name)
def num2Attrs = jar2File.manifest.mainAttributes.size()

println ROW_SEPARATOR
println "| ${file1Name.center(COLUMN_WIDTH)}| ${file2Name.center(COLUMN_WIDTH)} |"
print "| ${(Integer.toString(num1Attrs) + (num1Attrs != 1 ? " attributes" : " attribute")).center(COLUMN_WIDTH)}|"
println " ${(Integer.toString(num2Attrs) + (num2Attrs != 1 ? " atttributes" : " attribute")).center(COLUMN_WIDTH)} |"
println ROW_SEPARATOR

if (jar1File.manifest != jar2File.manifest)
{
   def manifest1Attrs = jar1File.manifest.mainAttributes
   def manifest2Attrs = jar2File.manifest.mainAttributes
   def attrsIn1ButNot2 = manifest1Attrs.keySet() - manifest2Attrs.keySet()
   def attrsIn2ButNot1 = manifest2Attrs.keySet() - manifest1Attrs.keySet()
   attrsIn1ButNot2.each
   {
      def attr1onlyStr = "${it}=${manifest1Attrs.get(it)}" 
      print "| ${attr1onlyStr.center(COLUMN_WIDTH)}| "
      println "${" ".center(attr1onlyStr.size() > COLUMN_WIDTH ? 2 * COLUMN_WIDTH - attr1onlyStr.size() : COLUMN_WIDTH)} |"
   }
   println ROW_SEPARATOR
   attrsIn2ButNot1.each
   {
      def attr2onlyStr = "${it}=${manifest2Attrs.get(it)}"
      print "| ${" ".center(attr2onlyStr.size() > COLUMN_WIDTH ? 2 * COLUMN_WIDTH - attr2onlyStr.size() : COLUMN_WIDTH)}|"
      println " ${attr2onlyStr.center(COLUMN_WIDTH)} |"
   }
   println ROW_SEPARATOR
   manifest1Attrs.each
   {
      def key = it.key
      if (it.value != manifest2Attrs.get(key) && !attrsIn1ButNot2.contains(it.key))
      {
         def attr1Str = "${key}=${manifest1Attrs.get(key)}"
         print "| ${attr1Str.center(COLUMN_WIDTH)}"
         def attr2Str = "${key}=${manifest2Attrs.get(key)}"
         println "| ${attr2Str.center(COLUMN_WIDTH)} |"
      }
   }
   println ROW_SEPARATOR
}
else
{
   println "Manifests deemed identical."
}

The script shown here makes it easy to quickly see the differences between contents of Manifest files of two JARs. Common characteristics of containing JAR's name and number of attributes in each Manifest file are shown in the script. Other manifest attributes are only displayed in the output if the attribute is unique to one Manifest file or if an attribute with the same name in each Manifest file has a different value for that attribute.

The above script could be combined with the jarDiff.groovy script I blogged on previously to see how two Manifest files differ when that script identifies that there are differences.

This is another example of why Groovy is such a useful scripting language in Java development environments.

No comments: