Saturday, March 10, 2012

Call Grand Parent method in Java

This is my very first blog. Help me to make it better.

Last week one of the java interviewer asked my friend whether you can call a grand parent method if method is overridden in parent class ? As like most other java developers know, he replied no, you can’t directly call grand parent method if it’s overridden in child class and I also agreed with him.

I know it’s not a supported feature in java language but I really wanted to see if I can do it anyway.
The only thing that came into my mind was reflection. I tried to retrieve Method instance from Parent Class and passed instance of child class, but Java still invoked overridden method of child class. No way you can do it with reflection.

To execute parent class method you always need to call it as super.method(). So how this super.method() makes difference than normal this.method() ?

public void test() {
 super.test();
 this.test();
}

So lets decompile bytecode for above test method.
public void test();
Code:
0:    aload_0
1:    invokespecial    #15; //Method AI1.test:()V
4:    aload_0
5:    invokevirtual    #17; //Method test:()V
8:    return

Wow, so when I called super.test(), that gets translated into 'invokespecial' bytecode and other method call gets translated into 'invokevirtual' bytecode.

Do you think with this knowledge of bytecodes, you can play any trick to call grand parent method ?

Yes, we can do it with the help of ASM. Using ASM, call test() method of Ancestor class with 'invokespecial'

Now let's see our class hierarchy.

public class AI {
 public void test() {
  System.out.println("test of AI");
 }
}

public class AI1 extends AI{
 public void test() {
  System.out.println("test of AI-1");
 }
}

public class AI2 extends AI1{
 public void test() {
  System.out.println("test of AI-2");
 }
}

Here Class AI2 is extending from AI1 and AI1 is extending from AI. Also method test() is overridden in all classes that means calling test() method on instance of AI2 class will result into executing test() method of AI2 only.

Now it's time to write down my ASM code. Using ASM, you can modify any existing class as well. But for simplicity I am creating here one more class Example, which is extending from AI2 class and also has overridden test() method.

import java.io.FileOutputStream;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class ASMByteCodeManipulation extends ClassLoader implements Opcodes {

 public static void main(String args[]) throws Exception {
  ClassWriter cw = new ClassWriter(0);
  cw.visit(V1_1, ACC_PUBLIC, "Example", null, "AI2", null);

  // creates a MethodWriter for the (implicit) constructor
  MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,null);
  mw.visitVarInsn(ALOAD, 0);
  mw.visitMethodInsn(INVOKESPECIAL, "AI2", "<init>", "()V");
  mw.visitInsn(RETURN);
  mw.visitMaxs(1, 1);
  mw.visitEnd();

  // creates a MethodWriter for the 'test' method
  mw = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
  mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out","Ljava/io/PrintStream;");
  mw.visitLdcInsn("test of AI3");
  mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
    "(Ljava/lang/String;)V");
  //Call test() of AI
  mw.visitVarInsn(ALOAD, 0);
  mw.visitMethodInsn(INVOKESPECIAL, "AI", "test", "()V");
  //Call test() of AI
  mw.visitVarInsn(ALOAD, 0);
  mw.visitMethodInsn(INVOKESPECIAL, "AI1", "test", "()V");
  //Call test() of AI
  mw.visitVarInsn(ALOAD, 0);
  mw.visitMethodInsn(INVOKESPECIAL, "AI2", "test", "()V");
  mw.visitInsn(RETURN);
  mw.visitMaxs(2, 1);
  mw.visitEnd();

  byte[] code = cw.toByteArray();
  FileOutputStream fos = new FileOutputStream("Example.class");
  fos.write(code);
  fos.close();

  ASMByteCodeManipulation loader = new ASMByteCodeManipulation();
  Class<?> exampleClass = loader.defineClass("Example", code, 0,
    code.length);
  Object obj = exampleClass.newInstance();
  exampleClass.getMethod("test", null).invoke(obj, null);

 }
}

and here is the output of above class
test of AI3
test of AI
test of AI-1
test of AI-2
Lets see now, how we achieved this.
On line 11, we defined new class Example which is extending from AI2 class. We also need to write down constructor. On line 22, our new Example class is overriding test() method where it first prints "test of AI3" and then invokes test() method of AI, AI1 & AI2 class on line 29,32 & 35 respectively. In main method, on line 45 it's creating an instance of Example class and invoking its test method using reflection.

Our new Example class is also written to Example.class file. Let's decode it's test() method
public void test();
Code:
0:    getstatic    #15; //Field java/lang/System.out:Ljava/io/PrintStream;
3:    ldc    #17; //String test of AI3
5:    invokevirtual    #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8:    aload_0
9:    invokespecial    #27; //Method AI.test:()V
12:    aload_0
13:    invokespecial    #30; //Method AI1.test:()V
16:    aload_0
17:    invokespecial    #31; //Method AI2.test:()V
20:    return

yes, on line 7,9 & 11 you can see it's invoking test() method of AI, AI1 & AI2 class respectively.

Above approach has limitations as well. invokespecial can be invoked from child class only. If you will try to use 'invokespecial' from non-child class,JVM runtime will fail to load your class with error message as
Exception in thread "main" java.lang.VerifyError: (class: Example, method: test1 signature: (LAI2;)V) Illegal use of nonvirtual function call

Yes, our job is done. Even though java language specification does not support invoking grand parent method if it is overridden in parent class, with the help of few bytecode manipulation we can always do it.

Note :- Above demonstration is just to show you capabilities of byte code manipulation. This solution is not advised for your day to day development.