How to get class which invokes static method?

Pavel_K :

This is my code:

class A {

    public static void doIt() {
        //Class currentClass = new Object() { }.getClass().getEnclosingClass();
        //Class currentClass = MethodHandles.lookup().lookupClass();
        String currentClass = Thread.currentThread().getStackTrace()[1].getClassName();
        System.out.println("CALLING CLASS:" + currentClass);
    }
}

class B extends A { }

public class NewMain {

    public static void main(String[] args) {
        A.doIt();
        B.doIt();
    }
}

As you see doIt method can be called by A and B classes. In doIt I want to know what class was used to call method (A or B). Is it possible? Three solutions I tried didn't work - it always says A class.

Johannes Kuhn :

At first, I thought this is impossible as the java compiler can figure out which method will be called and emit the same instruction.

Turns out that it actually records the way the class is called.

So, the question now becomes:

  • How do we get the place where the method is called?
  • How do we use this information to get the way the method is called?

The first one is easy: We use a StackWalker, which can give us the bytecode index.

Now we only need to parse the class, look at the instruction at that bytecode index, and figure out how this method was called.

I used ASM for that, but it might be the wrong tool here.

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.StackWalker.StackFrame;
import java.util.Set;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import static org.objectweb.asm.Opcodes.*;

class CallingClassVisitor extends ClassVisitor {

    private final StackFrame where;
    String ownerClass = null;

    public CallingClassVisitor(StackFrame where) {
        // We need a backing ClassWriter, so the Label is resolved.
        super(ASM8, new ClassWriter(0));
        this.where = where;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
            String[] exceptions) {
        MethodVisitor parent = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (name.equals(where.getMethodName()) && descriptor.equals(where.getDescriptor())) {
            return new CallingMethodVisitor(where, parent);
        } else {
            return parent;
        }
    }

    class CallingMethodVisitor extends MethodVisitor {

        private final StackFrame where;
        public CallingMethodVisitor(StackFrame where, MethodVisitor parent) {
            super(ASM8, parent);
            this.where = where;
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            Label lbl = new Label();
            visitLabel(lbl);
            if (lbl.getOffset() == where.getByteCodeIndex()) {
                ownerClass = owner; 
            }
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
        }
    }

    public String getOwnerClass() {
        return ownerClass;
    }
}



class A {

    static final StackWalker SW = StackWalker.getInstance(Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE));

    public static void doIt() {
        StackFrame sf = SW.walk(s -> s.skip(1).findFirst()).orElseThrow();
        InputStream source = sf.getDeclaringClass().getClassLoader()
                .getResourceAsStream(sf.getClassName().replace('.', '/') + ".class");
        try {
            CallingClassVisitor ccv = new CallingClassVisitor(sf);
            new ClassReader(source).accept(ccv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
            String how = ccv.getOwnerClass();
            System.out.println(how);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

    }
}

class B extends A { }

public class NewMain {

    public static void main(String[] args) {
        A.doIt();
        B.doIt();
    }
}

In the end, I'm not sure if your requirement is worth that effort.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=400335&siteId=1