2 Replies Latest reply on Dec 10, 2005 4:46 PM by chiba

    Modifying CtClass

    starksm64

      I'm trying to use the following code fragment to write out multiple versions of a classes bytecode to a test classpath:

       File libDir = new File(jbosstestDeployDir);
       File classes1 = new File(libDir, "classes1");
       classes1.mkdir();
      
       // Create a test.Info class with a static String version = "Version 1.0"
       ClassPool pool = ClassPool.getDefault();
       CtClass info = pool.makeClass("test.Info");
       // Don't let javassist optimize the class so we can modify it after writing it out
       info.stopPruning(true);
       CtClass s = pool.get("java.lang.String");
       CtField version = new CtField(s, "version", info);
       version.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
       info.addField(version, CtField.Initializer.constant("Version 1.0"));
       info.writeFile(classes1.getAbsolutePath());
      
       // Create a test.Info class with a static String version = "Version 2.0"
       File classes2 = new File(libDir, "classes2");
       classes2.mkdir();
       info.defrost();
       info.removeField(version);
       version = new CtField(s, "version", info);
       version.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
       info.addField(version, CtField.Initializer.constant("Version 2.0"));
       info.writeFile(classes2.getAbsolutePath());
      


      The intent is that the classes1/test/Info.class corresponds to:
      package test;
      public class Info
      {
       public static String version = "Version 1.0";
      }
      


      while classes2/test/Info.class corresponds to:
      package test;
      public class Info
      {
       public static String version = "Version 2.0";
      }
      


      The javap output from the classes2/test/Info.class is showing that the version field is being initialized twice, once with the expected "Version 2.0" string and then this is overwritten with the old ""Version 1.0" value:

      [starksm@banshee9100 testsuite]$ javap -verbose -classpath output/lib/classes2 test.Info
      Compiled from "Info.java"
      public class test.Info extends java.lang.Object
       SourceFile: "Info.java"
       minor version: 3
       major version: 45
       Constant pool:
      const #1 = Asciz test/Info;
      const #2 = class #1; // test/Info
      const #3 = Asciz java/lang/Object;
      const #4 = class #3; // java/lang/Object
      const #5 = Asciz <init>;
      const #6 = Asciz ()V;
      const #7 = Asciz Code;
      const #8 = class #3; // java/lang/Object
      const #9 = NameAndType #5:#6;// "<init>":()V
      const #10 = Method #8.#9; // java/lang/Object."<init>":()V
      const #11 = Asciz <clinit>;
      const #12 = Asciz Version 1.0;
      const #13 = String #12; // Version 1.0
      const #14 = class #1; // test/Info
      const #15 = Asciz version;
      const #16 = Asciz Ljava/lang/String;;
      const #17 = NameAndType #15:#16;// version:Ljava/lang/String;
      const #18 = Field #14.#17; // test/Info.version:Ljava/lang/String;
      
      const #19 = Asciz SourceFile;
      const #20 = Asciz Info.java;
      const #21 = Asciz Version 2.0;
      const #22 = String #21; // Version 2.0
      const #23 = NameAndType #15:#16;// version:Ljava/lang/String;
      const #24 = Field #2.#23; // test/Info.version:Ljava/lang/String;
      
      {
      public static java.lang.String version;
      
      public test.Info();
       Code:
       Stack=1, Locals=1, Args_size=1
       0: aload_0
       1: invokespecial #10; //Method java/lang/Object."<init>":()V
       4: return
      
      static {};
       Code:
       Stack=1, Locals=0, Args_size=0
       0: ldc #22; //String Version 2.0
       2: putstatic #24; //Field version:Ljava/lang/String;
       5: ldc #13; //String Version 1.0
       7: putstatic #18; //Field test/Info.version:Ljava/lang/String;
       10: return
      
      }
      


      Is that expected?

      Is there a variation of this code fragment that will produce the desired output bytecode?


        • 1. Re: Modifying CtClass
          starksm64

          The following works:

           // Create a test.Info class with a static String version = "Version 1.0"
           ClassPool defaultPool = ClassPool.getDefault();
           ClassPool classes1Pool = new ClassPool(defaultPool);
           CtClass info = classes1Pool.makeClass("test.Info");
           // Don't let javassist optimize the class so we can modify it after writing it out
           info.stopPruning(true);
           CtClass s = classes1Pool.get("java.lang.String");
           CtField version = new CtField(s, "version", info);
           version.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
           info.addField(version, CtField.Initializer.constant("Version 1.0"));
           info.writeFile(classes1.getAbsolutePath());
          
           // Create a test.Info class with a static String version = "Version 2.0"
           ClassPool classes2Pool = new ClassPool(defaultPool);
           info = classes2Pool.makeClass("test.Info");
           File classes2 = new File(libDir, "classes2");
           classes2.mkdir();
           version = new CtField(s, "version", info);
           version.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
           info.addField(version, CtField.Initializer.constant("Version 2.0"));
           info.writeFile(classes2.getAbsolutePath());
          



          • 2. Re: Modifying CtClass
            chiba

            That's spec. Because the field initialization is done in a static initializer,
            when you remove a field, you also have to remove the initialization code
            from the static initializer, which is not a simple task!.

            So your alternative code (create another ClassPool) is a right solution.