Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javassist - How to inject a method into a class in JAR

Tags:

java

jar

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)

like image 886
MarkW Avatar asked Mar 23 '14 14:03

MarkW


2 Answers

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.

like image 50
MarkW Avatar answered Oct 05 '22 23:10

MarkW


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.

like image 41
Markus Avatar answered Oct 05 '22 23:10

Markus