Статьи

Сравнение JAR с Groovy

Иногда бывает полезно сравнить содержимое двух файлов JAR. В этой записи блога я продемонстрирую скрипт Groovy, который действует как простой «diff» инструмент для сравнения двух файлов JAR . Скрипт Groovy, показанный здесь, jarDiff.groovy , несомненно, может быть улучшен, но он выполняет то, что я хотел. Сценарий сравнивает два предоставленных JAR следующими способами:

  • Показывает путь, имя и размер обоих JAR-файлов независимо от того, являются ли они одинаковыми или разными.
  • Показывает записи в каждом JAR, которые не существуют в другом JAR
  • Показывает записи, которые являются общими (по имени) в каждом JAR, но имеют разные атрибуты (CRC, размер или дата изменения)

Приведенные выше характеристики выходных данных сценария означают, что для идентичных JAR-файлов отображаются только путь / имя файла каждого JAR-файла и размер каждого JAR-файла. Для разных JAR-файлов те же атрибуты будут отображаться вместе с записями, которые существуют только в одном JAR-файле, а не с другим, и записями, общими для двух JAR-файлов с разными CRC, размером или датой изменения. Важное различие, которое следует сделать в отношении этого сценария, заключается в том, что он в основном полезен для сравнения метаданных в двух JAR-файлах и не обеспечивает различий на уровне методов / API (как было бы обеспечено инструментом, таким как javap ) или на уровне исходного кода. (потребуется декомпилятор). Этот сценарий определяет наличие различий, и эти другие инструменты могут затем использоваться для изучения более глубоких деталей различий.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/env groovy
 
/**
 * jarDiff.groovy
 *
 * jarDiff.groovy <first_jar_file> <second_jar_file>
 *
 * Script that compares to JAR files, reporting basic characteristics of each
 * along with differences between the two JARs.
 */
 
if (args.length < 2)
{
   println "\nUSAGE: jarDiff.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 jar1 = extractJarContents(jar1File)
def file2Name = args[1]
def jar2File = new JarFile(file2Name)
def jar2 = extractJarContents(jar2File)
 
def entriesInJar1ButNotInJar2 = jar1.keySet() - jar2.keySet()
def entriesInJar2ButNotInJar1 = jar2.keySet() - jar1.keySet()
 
println ROW_SEPARATOR
println "| ${file1Name.center(COLUMN_WIDTH)} |${file2Name.center(COLUMN_WIDTH)} |"
print "| ${(Integer.toString(jar1File.size()) + " bytes").center(COLUMN_WIDTH)} |"
println "${(Integer.toString(jar2File.size()) + " bytes").center(COLUMN_WIDTH)} |"
println ROW_SEPARATOR
 
if (jar1File.manifest != jar2File.manifest)
{
   def manifestPreStr = "# Manifest Entries: "
   def manifest1Str = manifestPreStr + Integer.toString(jar1File.manifest.mainAttributes.size())
   print "| ${manifest1Str.center(COLUMN_WIDTH)} |"
   def manifest2Str = manifestPreStr + Integer.toString(jar2File.manifest.mainAttributes.size())
   println "${manifest2Str.center(COLUMN_WIDTH)} |"
   println ROW_SEPARATOR
}
 
entriesInJar1ButNotInJar2.each
{ entry1 ->
   print "| ${entry1.center(COLUMN_WIDTH)} |"
   println "${" ".center(entry1.size() > COLUMN_WIDTH ? 2 * COLUMN_WIDTH - entry1.size() : COLUMN_WIDTH)} |"
   println ROW_SEPARATOR
}
entriesInJar2ButNotInJar1.each
{ entry2 ->
   print "| ${" ".center(entry2.size() > COLUMN_WIDTH ? 2 * COLUMN_WIDTH - entry2.size() : COLUMN_WIDTH)}"
   println "| ${entry2.center(COLUMN_WIDTH)} |"
   println ROW_SEPARATOR
}
 
jar1.each
{ key, value ->
   if (!entriesInJar1ButNotInJar2.contains(key))
   {
      def jar2Entry = jar2.get(key)
      if (value != jar2Entry)
      {
         println "| ${key.center(COLUMN_WIDTH)} |${jar2Entry.name.center(COLUMN_WIDTH)} |"
         if (value.crc != jar2Entry.crc)
         {
            def crc1Str = "CRC: ${value.crc}"
            def crc2Str = "CRC: ${jar2Entry.crc}"
            print "| ${crc1Str.center(COLUMN_WIDTH)} |"
            println "${crc2Str.center(COLUMN_WIDTH)} |"
         }
         if (value.size != jar2Entry.size)
         {
            def size1Str = "${value.size} bytes"
            def size2Str = "${jar2Entry.size} bytes"
            print "| ${size1Str.center(COLUMN_WIDTH)} |"
            println "${size2Str.center(COLUMN_WIDTH)} |"
         }
         if (value.time != jar2Entry.time)
         {
            def time1Str = "${new Date(value.time)}"
            def time2Str = "${new Date(jar2Entry.time)}"
            print "| ${time1Str.center(COLUMN_WIDTH)} |"
            println "${time2Str.center(COLUMN_WIDTH)} |"
         }
         println ROW_SEPARATOR
      }
   }
}
 
/**
 * Provide mapping of JAR entry names to characteristics about that JAR entry
 * for the JAR indicated by the provided JAR file name.
 *
 * @param jarFile JAR file from which to extract contents.
 * @return JAR entries and thir characteristics.
 */
def TreeMap<String, JarCharacteristics> extractJarContents(JarFile jarFile)
{
   def jarContents = new TreeMap<String, JarCharacteristics>()
   entries = jarFile.entries()
   entries.each
   { entry->
      jarContents.put(entry.name, new JarCharacteristics(entry.name, entry.crc, entry.size, entry.time));
   }
   return jarContents
}

Как и все скрипты Groovy, вышеперечисленное может быть написано на Java, но Groovy лучше подходит для написания скриптов, чем на Java. Вышеупомянутый скрипт Groovy использует функции Groovy, которые я рассмотрел в предыдущих публикациях в блоге, такие как Отчеты со сценариями с Groovy (для форматирования вывода различий) и Поиск файлов JAR с помощью Groovy (для просмотра и чтения файлов JAR).

Есть несколько потенциальных улучшений для этого скрипта. Они включают в себя, чтобы скрипт показывал различия в файлах MANIFEST.MF, помимо различий, обнаруженных во всех файлах в JAR, путем сравнения содержимого одного файла манифеста с другим. Другие улучшения могут использовать сравнение методов, определенных в классах / интерфейсах / перечислениях, содержащихся в JAR-файлах, с помощью отражения. На данный момент, однако, я согласен использовать javap или javac -Xprint, чтобы увидеть изменения метода, как только приведенный выше скрипт идентифицирует различия в конкретном классе, перечислении или интерфейсе.

Возможность быстрой идентификации различий между двумя JAR-файлами может быть полезна в различных обстоятельствах, таких как сравнение версий собственных сгенерированных JAR-файлов для изменений или сравнение JAR-файлов предоставленных библиотек и сред, которые не названы таким образом, чтобы сделать их различия очевидными , Скрипт Groovy, продемонстрированный в этом посте, идентифицирует различия на высоком уровне между двумя JAR-файлами и в то же время демонстрирует некоторые приятные возможности Groovy.

Ссылка: сравнение JAR-файлов с Groovy от нашего партнера по JCG Дастина Маркса в блоге Inspired by Actual Events .