To find the difference between two Java JAR files, you can
use various methods and tools to compare their contents. Here are a few
approaches:
1. Using jar command-line tool (shipped with JDK):
You can extract the contents of both JAR files and then
compare the extracted directories.
Steps:
Ø Extract
the contents of the JAR files:
Bash
jar -xf
file1.jar jar -xf
file2.jar |
Ø Use
a file comparison tool to compare the extracted directories:
bash
diff -r dir1
dir2 |
This will show differences in files and subdirectories
between the two JARs.
2. Using jardiff Tool (from Apache):
jardiff is a simple command-line tool used to compare two
JAR files and output their differences.
Steps:
Ø Install
and run jardiff:
Bash
java -jar
jardiff.jar file1.jar file2.jar |
Ø It
will provide a detailed report of differences, including added, removed, and
changed files.
3. Using JAR Compare Plugin in IDEs:
Many IDEs like IntelliJ IDEA and Eclipse offer plugins or
built-in features to compare JAR files.
In IntelliJ:
Ø Open
the Project View.
Ø Select
the two JAR files.
Ø Right-click
and choose "Compare Archives."
This will show differences in class files, resources, and
even method-level changes.
4. Using Beyond Compare or WinMerge:
If you want a graphical interface, tools like Beyond
Compare or WinMerge allow you to compare JARs (which are essentially
zip files).
Steps:
Ø Open
the tool.
Ø Load
both JAR files for comparison.
Ø These
tools will highlight the differences in the classes, manifest files, etc.
5. Using jarsigner and javap:
For more granular comparisons, you can extract the bytecode
from the JARs and compare them:
Ø Use jarsigner
to verify signatures:
bash
jarsigner
-verify -verbose file1.jar jarsigner
-verify -verbose file2.jar |
Ø Use javap
to disassemble class files:
Bash
javap -c
-verbose classFile1.class > classFile1.txt javap -c
-verbose classFile2.class > classFile2.txt |
Ø Then,
compare the .txt files to see method-level or bytecode differences.
6. Java utility that analyzes and compare two jar files:
package
com.kartik; import
java.io.FileInputStream; import
java.util.ArrayList; import
java.util.HashMap; import
java.util.HashSet; import
java.util.List; import
java.util.Map; import
java.util.Set; import
java.util.TreeSet; import java.util.jar.Attributes; import
java.util.jar.JarEntry; import
java.util.jar.JarInputStream; import
java.util.jar.Manifest; import
org.objectweb.asm.ClassReader; import
org.objectweb.asm.Opcodes; import
org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import
org.objectweb.asm.tree.MethodNode; public class
Demo { static Map<String, Set<String>>
mapOld = new HashMap<String, Set<String>>(); static Map<String, Set<String>>
mapNew = new HashMap<String, Set<String>>(); /** * * @param setA * @param setB * @return */ public static Set<String>
union(Set<String> setA, Set<String> setB) { Set<String> tmp = new
TreeSet<String>(setA); tmp.addAll(setB); return tmp; } /** * * @param setA * @param setB * @return */ public static Set<String>
intersection(Set<String> setA, Set<String> setB) { Set<String> tmp = new
TreeSet<String>(); for (String x : setA) if (setB.contains(x)) tmp.add(x); return tmp; } /** * * @param setA * @param setB * @return */ public static Set<String>
difference(Set<String> setA, Set<String> setB) { Set<String> tmp = new
TreeSet<String>(setA); tmp.removeAll(setB); return tmp; } /** * * @param setA * @param setB * @return */ public static Set<String>
symDifference(Set<String> setA, Set<String> setB) { Set<String> tmpA; Set<String> tmpB; tmpA = union(setA, setB); tmpB = intersection(setA, setB); return difference(tmpA, tmpB); } /** * * @param jarFileList */ public void
getClassNamesFromJar(List<String> jarFileList) { int count = 0; for (String string : jarFileList) { count++; String jarFileName = string; System.out.println("Jar File Name:
" + jarFileName); System.out.println("-- -- -- -- -- --
-- -- -- -- -- -- -- -"); try { JarInputStream jarFile = new
JarInputStream( new FileInputStream(jarFileName)); JarEntry entry; Manifest m = jarFile.getManifest(); Attributes atts = m.getMainAttributes(); for (Map.Entry<Object, Object>
entrys : atts.entrySet()) { System.out.println(entrys.getKey() +
"-->>>> " + entrys.getValue()); } while (true) { entry = jarFile.getNextJarEntry(); if (entry == null) { break; } if
((entry.getName().endsWith(".class"))) { ClassNode classNode = new ClassNode(); try { ClassReader classReader = new ClassReader(jarFile); classReader.accept(classNode, 0); } catch (Exception e3) { jarFile.close(); } describeClass(classNode, count); //
System.out.println(describeClass(classNode, count)); } } } catch (Exception e) { System.out .println("Oops.. Encounter an
issue while parsing jar" + e.toString()); } } } /** * * @param classNode * @param count * @return */ public String describeClass(ClassNode
classNode, int count) { StringBuilder classDescription = new
StringBuilder(); Type classType =
Type.getObjectType(classNode.name); if ((classNode.access &
Opcodes.ACC_PUBLIC) != 0) { classDescription.append("public
"); } if ((classNode.access &
Opcodes.ACC_PRIVATE) != 0) { classDescription.append("private
"); } if ((classNode.access &
Opcodes.ACC_PROTECTED) != 0) { classDescription.append("protected
"); } if ((classNode.access &
Opcodes.ACC_ABSTRACT) != 0) { classDescription.append("abstract
"); } if ((classNode.access &
Opcodes.ACC_INTERFACE) != 0) { classDescription.append("interface
"); } else { classDescription.append("class
"); }
classDescription.append(classType.getClassName()).append("\n"); classDescription.append("{\n"); // The method signatures (e.g. - // "public static void main(String[])
throws Exception") @SuppressWarnings("unchecked") List<MethodNode> methodNodes =
classNode.methods; Set<String> methodHold = new
HashSet<String>(); for (MethodNode methodNode : methodNodes) { String methodDescription =
describeMethod(methodNode); methodHold.add(methodDescription);
classDescription.append("\t").append(methodDescription) .append("\n"); } if (count % 2 == 0) { mapOld.put(classType.getClassName(),
methodHold); } else { mapNew.put(classType.getClassName(),
methodHold); } classDescription.append("}\n"); return classDescription.toString(); } /** * * @param methodNode * @return */ public String describeMethod(MethodNode
methodNode) { StringBuilder methodDescription = new
StringBuilder(); Type returnType =
Type.getReturnType(methodNode.desc); Type[] argumentTypes =
Type.getArgumentTypes(methodNode.desc); @SuppressWarnings("unchecked") List<String> thrownInternalClassNames
= methodNode.exceptions; if ((methodNode.access &
Opcodes.ACC_PUBLIC) != 0) { methodDescription.append("public
"); // } /* * * if ((methodNode.access &
Opcodes.ACC_PRIVATE) != 0) { * * methodDescription.append("private
"); } * * * * if ((methodNode.access &
Opcodes.ACC_PROTECTED) != 0) { * *
methodDescription.append("protected "); } * * * * if ((methodNode.access &
Opcodes.ACC_STATIC) != 0) { * * methodDescription.append("static
"); } * * * * if ((methodNode.access &
Opcodes.ACC_ABSTRACT) != 0) { * * methodDescription.append("abstract
"); } * * * * if ((methodNode.access &
Opcodes.ACC_SYNCHRONIZED) != 0) * * { * *
methodDescription.append("synchronized "); } */ methodDescription.append(returnType.getClassName()); methodDescription.append(" "); methodDescription.append(methodNode.name); methodDescription.append("("); for (int i = 0; i <
argumentTypes.length; i++) { Type argumentType = argumentTypes[i]; if (i > 0) { methodDescription.append(",
"); }
methodDescription.append(argumentType.getClassName()); } methodDescription.append(")"); /* * * if (!thrownInternalClassNames.isEmpty())
{ * * methodDescription.append(" throws
"); int i = 0; for * * (String * * thrownInternalClassName :
thrownInternalClassNames) { if * * (i > 0) * * { methodDescription.append(",
"); } * * methodDescription.append(Type. * *
getObjectType(thrownInternalClassName).getClassName()); * * i++; } } */ } return methodDescription.toString(); } public static void main(String[] args) { Demo d = new Demo(); List<String> jarFileName = new
ArrayList<String>();
jarFileName.add("D:\\External_Jar\\Compare\\old\\XmlSchema-1.4.2.jar"); jarFileName
.add("D:\\External_Jar\\Compare\\new\\xmlschema-core-
2.2.1.jar"); d.getClassNamesFromJar(jarFileName); /* * * boolean flag=false; StringBuilder
classDescription = new * * StringBuilder(); for
(Map.Entry<String, Set<String>> entryOld : * * mapOld.entrySet()) { for
(Map.Entry<String, Set<String>> * * entryNew : * * mapNew.entrySet()) { * *
if(entryOld.getKey().equalsIgnoreCase(entryNew.getKey())){ * * flag=true; * * } } if(!flag){ * * classDescription.append("Add new
Class in latest Jar " * * +entryOld.getKey()).append("\n"); * *
classDescription.append("{\n"); * * //System.out.println("-- -- -- --
New-- -- -- -- -- - * * "+entryOld.getKey()+ * * "-- -- -- -- -- -- -- -- -- --
"); Set<String> * val=entryOld.getValue(); * * for * * (String string : val) { * *
classDescription.append("\t").append(string).append("\n"); * * //System.out.println(string); }
classDescription.append("}\n"); * * }else{ flag=false; } } * * System.out.println(classDescription.toString()); */ System.out.println(); StringBuilder classDescriptionSecond = new
StringBuilder(); boolean falgVal = false; System.out
.println("-#############################################################-"); System.out .println("Remove Some Class in
latest jar that list give below:"); System.out
.println("-#############################################################-"); int i = 0; for (Map.Entry<String,
Set<String>> entryNew : mapNew.entrySet()) { for (Map.Entry<String,
Set<String>> entryOld : mapOld.entrySet()) { if
(entryOld.getKey().equalsIgnoreCase(entryNew.getKey())) { falgVal = true; } } if (!falgVal) { classDescriptionSecond.append(i + "
" + entryNew.getKey()) .append("\n");
classDescriptionSecond.append("{\n"); Set<String> val =
entryNew.getValue(); for (String string : val) { classDescriptionSecond.append("\t").append(string) .append("\n"); }
classDescriptionSecond.append("}\n"); i++; } else { falgVal = false; } }
System.out.println(classDescriptionSecond.toString()); System.out.println(); System.out
.println("-#############################################################-"); System.out .println("Matches Class in latest
and old but some method have changes:"); System.out .println("###############################################################-"); StringBuilder classDescriptionThird = new
StringBuilder(); int j = 0; for (Map.Entry<String,
Set<String>> entryNew : mapNew.entrySet()) { for (Map.Entry<String,
Set<String>> entryOld : mapOld.entrySet()) { if
(entryOld.getKey().equalsIgnoreCase(entryNew.getKey())) { classDescriptionThird.append(j + "
" + entryNew.getKey()) .append("\n");
classDescriptionThird.append("{\n"); Set<String> valOld =
entryOld.getValue(); Set<String> valNew =
entryNew.getValue(); Set<String> temp =
intersection(valOld, valNew); Set<String> addMethodInLatest = difference(valOld, temp); Set<String> removeMethodInLatest = difference(valNew, temp); for (String string :
removeMethodInLatest) { classDescriptionThird .append("\t") .append("Update/remove old method
in Latest Jar ->") .append(string).append("\n"); } /* * * this is need to display of how many
new * * method add for * * (String string : addMethodInLatest) { * * classDescriptionThird * *
.append("\t").append("Add new method in * * Latest Jar -- >" * *
).append(string).append("\n"); } */
classDescriptionThird.append("}\n"); j++; } } }
System.out.println(classDescriptionThird.toString()); } } |
This Java code is a utility that analyzes and compares two
JAR files, focusing on the classes and methods contained within them. The
primary purpose is to identify differences between the two versions of the JAR
files.
Key Components:
- Utility
Methods:
Ø
union, intersection, difference,
symDifference: These methods are used to perform set operations on the
collections of methods or classes from the JAR files.
- getClassNamesFromJar
Method:
Ø
This method takes a list of JAR files, reads the
class entries, and parses them using ASM (a Java bytecode manipulation
framework).
Ø
It gathers the classes and methods from the JAR
file, storing them in maps (mapOld for the old version and mapNew for the new
version).
- describeClass
Method:
Ø
This method describes the class, including its
access modifiers and methods, and populates the appropriate map depending on
whether it is analyzing the old or new JAR.
- describeMethod
Method:
Ø
This method formats and returns the method's
signature, including its access modifiers, return type, and parameter types.
- Comparison
Logic:
Ø
The main method of the Demo class compares the
contents of the two maps (mapOld and mapNew) to determine:
ü
Which classes are present in the new JAR but not
in the old one.
ü
Which classes are present in both but have
different methods.
ü
It also identifies methods that have been
removed or added.
Example of Usage:
The main method sets up the analysis by comparing two
specific JAR files (XmlSchema-1.4.2.jar and xmlschema-core-2.2.1.jar), then
runs the comparison, outputting the differences.
Execution Flow:
- Parsing
JAR Files:
Ø
The getClassNamesFromJar method parses the JAR
files and extracts class information into maps.
- Comparing
Classes:
Ø
Classes that exist in the new JAR but not in the
old one are listed.
Ø
Classes that exist in both but have different
methods are identified, and the differences are described.
Output:
The program prints out:
- Classes
that were removed or added.
- Methods
that were updated or removed in classes that exist in both versions.
Notes:
- The
code uses ASM to read and manipulate class files, which is common for
tasks like bytecode analysis and modification.
- Error
handling could be improved for robustness, especially around the file
handling and ASM operations.
If you need to run this code, ensure you have the ASM
library included in your classpath, and adjust the file paths for the JAR files
you wish to compare.
0 Comments