0

In the example below, I want to execute the super method of class A by not executing the super method of class B. For this, I am using Javassist and I am able to do so with the help of this SO post. But I am unable to pass a value when calling the super method of class A. I have provided a variable name as parameter, while calling hi() of class C in the example below but that is not being referred in the variable myVar that I am generating using Javassist.

The generated output:

Ok thing C MyData
Important thing A null

Expected output:

Ok thing C MyData
Important thing A MyData

Any help will be very much appreciated.

Source Code:

class A {
    public void hi(String first) {
        System.out.println("Want Important thing A " + first);
    }
}

class B extends A {
    public void hi(String second) {
        System.out.println("Don't want TERRIBLE THING B " + second);
        super.hi(second);
    }
}

class C extends B {
    public void hi(String third) {
        System.out.println("Want important thing C " + third);
        super.hi(third);
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        String name = "MyData";
        CtClass cc = ClassPool.getDefault().get("mypackage.B");
        CtMethod m1 = cc.getDeclaredMethod("hi");
        cc.removeMethod(m1);
        CtMethod m2 = CtNewMethod.copy(m1, cc, null);
        CtClass stringVar = ClassPool.getDefault().get("java.lang.String");
        CtField f = new CtField(stringVar , "myVar", cc);
        cc.addField(f);
        m2.setBody("{ /* override method B.hi() body */ return super.hi(myVar);}", "this", m1.getName());
        cc.addMethod(m2);
        cc.toClass();
        C obj = new C();
        obj.hi(name);
    }
}
sam
  • 1,800
  • 1
  • 25
  • 47
  • 1
    Did you try whether the code of the linked answer worked in the first place, before you made your changes? Using `return methodCall();` for `void` methods looks suspicious, but perhaps Javassist allows it (in contrast to standard Java). The answer does already point out that you have to modify the class *before* any use of it, but using it within the same method that is supposed to do the modification is very fragile. Further note that your output doesn’t even remotely match what you have written in the posted code. Copy the real thing or don’t be disappointed if no-one can help you… – Holger Sep 06 '21 at 13:56
  • @Holger Thank you for taking some time. Regarding the `return` statement, I did miss that part. Maybe because javassist was not throwing any exception. My problem is, while calling the `super.hi()` from class C, it shouldn't call `hi()` of class B but instead it should call `hi()` of class A. Now, in the **sysout** of class A, the value of variable `first` is getting `null`, instead of the value that was passed during the method call from the `main` function. I want that value to be passed to `hi()` of class A as well. Kindly excuse me for my English. – sam Sep 06 '21 at 17:08
  • 1
    Let’s assume that your approach actually worked. Then, `super.hi(myVar);` would pass the contents of the field `myVar`. Since there is no assignment to `myVar` anywhere, reading `null` is actually expected. When you want to pass the contents of a parameter instead, don’t add a field. According to [this tutorial](https://www.javassist.org/tutorial/tutorial2.html#before), you can access the first parameter using `$1`. This tutorial also mentions that Javassist indeed allows `return methodCall();` for `void` methods, so `"{ return super.hi($1); }"` should work as method body. – Holger Sep 06 '21 at 17:35
  • @Holger Thank you so much. This is exactly the solution I wanted. Now I am getting the expected result mentioned in the question. If you could add this as an answer, then i would accept that as the solution. – sam Sep 06 '21 at 19:54

1 Answers1

2

Since you want to pass the parameter value to the super method call, you don’t need to introduce a new field.

According to this tutorial you can refer to the first parameter using $1, regardless of the original source code’s name (which also solves the problem that the name might not be stored in the class file).

So the method body would look like "{ return super.hi($1); }". The tutorial also explains that Javassist, unlike ordinary Java source code, allows to use this syntax for void methods.

It’s important to keep in mind that the method of replacing classes by materializing an alternative version before the other has been loaded, is very fragile, especially when being done in the same method that also uses the changed class (or a subclass of it). This depends on the precise time of class loading whose subtleties have been discussed in When is a Java Class loaded?

Holger
  • 285,553
  • 42
  • 434
  • 765