I am reading a class from JAR
file and injecting a function. How do I write this back to the JAR file?
// Load the class representation
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath( "c:/Test.jar" );
CtClass cc = pool.get("com.test.TestFunction");
CtMethod m = CtNewMethod.make("public void test2() { System.out.println(\"test2\"); }", cc);
cc.addMethod(m);
CtMethod cm = cc.getDeclaredMethod("test1", new CtClass[0]);
cm.insertBefore("{ test2();}");
cc.writeFile("c:/Test.jar"); // Fails here
Exception in thread "main" java.io.FileNotFoundException
: c:\Test.jar\com\test\TestFunction.class (The system cannot find the path specified)
I guess there is no simple method in Javassist that would update a JAR and replace an updated class with a new class. So I created a JarHandler class that simply receives the parameters.
This is main class that does the injection
public static void main(String args[]){
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath( "c:/Test.jar" );
CtClass cc = pool.get("com.test.TestFunction");
CtMethod m = CtNewMethod.make("public void test2() { System.out.println(\"test2\"); }", cc);
cc.addMethod(m);
CtMethod cm = cc.getDeclaredMethod("test1", new CtClass[0]);
cm.insertBefore("{ test2();}");
byte[] b = cc.toBytecode(); // convert the new class to bytecode.
pool.removeClassPath(cp); // need to remove the classpath to release connection to JAR file so we can update it.
JarHandler jarHandler = new JarHandler();
jarHandler.replaceJarFile("C:/Test.jar", b, "com/test/TestFunction.class");
}
This is the JarHandler Class
public class JarHandler{
public void replaceJarFile(String jarPathAndName,byte[] fileByteCode,String fileName) throws IOException {
File jarFile = new File(jarPathAndName);
File tempJarFile = new File(jarPathAndName + ".tmp");
JarFile jar = new JarFile(jarFile);
boolean jarWasUpdated = false;
try {
=
JarOutputStream tempJar =
new JarOutputStream(new FileOutputStream(tempJarFile));
// Allocate a buffer for reading entry data.
byte[] buffer = new byte[1024];
int bytesRead;
try {
// Open the given file.
try {
// Create a jar entry and add it to the temp jar.
JarEntry entry = new JarEntry(fileName);
tempJar.putNextEntry(entry);
tempJar.write(fileByteCode);
} catch (Exception ex) {
System.out.println(ex);
// Add a stub entry here, so that the jar will close without an
// exception.
tempJar.putNextEntry(new JarEntry("stub"));
}
// Loop through the jar entries and add them to the temp jar,
// skipping the entry that was added to the temp jar already.
InputStream entryStream = null;
for (Enumeration entries = jar.entries(); entries.hasMoreElements(); ) {
// Get the next entry.
JarEntry entry = (JarEntry) entries.nextElement();
// If the entry has not been added already, so add it.
if (! entry.getName().equals(fileName)) {
// Get an input stream for the entry.
entryStream = jar.getInputStream(entry);
tempJar.putNextEntry(entry);
while ((bytesRead = entryStream.read(buffer)) != -1) {
tempJar.write(buffer, 0, bytesRead);
}
}else
System.out.println("Does Equal");
}
if(entryStream!=null)
entryStream.close();
jarWasUpdated = true;
}
catch (Exception ex) {
System.out.println(ex);
// IMportant so the jar will close without an
// exception.
tempJar.putNextEntry(new JarEntry("stub"));
}
finally {
tempJar.close();
}
}
finally {
jar.close();
if (! jarWasUpdated) {
tempJarFile.delete();
}
}
if (jarWasUpdated) {
if(jarFile.delete()){
tempJarFile.renameTo(jarFile);
System.out.println(jarPathAndName + " updated.");
}else
System.out.println("Could Not Delete JAR File");
}
}
This function simply creates a temporary JAR by writing the new class bytecode to it. Then is goes thru all entries of the current JAR and writes all its entries to the temporary JAR file except for the entry that is being updated (the bytecode was already written to it above). Then it removes the current JAR and replaces it with the temporary JAR using the same JAR name.
I want to reference to the followin StackOverflow Q&A: Link
When you read the comments you see that it is not possible to write back to a JAR. This is also indicated by the IOException (FNFE) that is thrown. Or a stupid wisenheimer answer: If we read the JavaDoc into that problem, we see that the method is expecting a directoryName. JavaDoc
I suggest to unzip the JAR, make the manipulations and then re-build (zip) the JAR.
Keep me posted on your thoughts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With