user5182503
user5182503

Reputation:

How to get class which invokes static method?

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.

Upvotes: 1

Views: 343

Answers (2)

F. Müller
F. Müller

Reputation: 4062

How about this one here? I know its not exactly what you want but as others already stated ... you might want to rethink your design approach.

class A {
    protected static Class childClass; // alt. private

    // protected static Class getChildClass { return childClass; }

    public static void setChildClass(Class classReference) {
        childClass = classReference;
    }

    public static void doIt() {
        System.out.println("CLASS:" + childClass.getSimpleName());
    }
}

class B extends A { 

    // doIt() will be inherited

    // overloading method with a param
    public static void doIt(String message) {
        System.out.println("CLASS:" + B.class.getSimpleName() + ", message: " + message);
    }
}

public class NewMain {

    public static void main(String[] args) {
        A.setChildClass(B.class);
        A.doIt(); // CLASS:B
        B.doIt(); // CLASS:B
        B.doIt("Hello") // CLASS:B, message: Hello
    }
}

EDIT

You could have a simple controller and a few delegates to do something similar. Although its not all just static, you have alot more control over the parts. Please note that the example is very basic on purpose.

You could also have a class A and a class B and B has A as a dependency and then you just override what A (the delegate) should do differently. Also here ... its not static, but I do not think that this is your main problem. Its more the design after all.

import java.util.*;

public class MyClass {
    public static void main(String... args) {
        final DoItController doItController = DoItController.Singleton.getInstance();
        doItController.doIt(DoerDelegateA.class);
        doItController.doIt(DoerDelegateB.class);
        doItController.doIt(DoerDelegateComplex.class);
    }
}

interface Doer {
    void doIt();
}

class DoItController {
    final Map<Class<? extends DoerDelegate>, DoerDelegate> doerDelegateMap;

    private DoItController() {
        doerDelegateMap = new LinkedHashMap();
        doerDelegateMap.put(DoerDelegateA.class, new DoerDelegateA());
        doerDelegateMap.put(DoerDelegateB.class, new DoerDelegateB());
        doerDelegateMap.put(DoerDelegateComplex.class, new DoerDelegateComplex());
    }

    public void doIt(Class<? extends DoerDelegate> delegateReference) {
        final DoerDelegate delegate = doerDelegateMap.getOrDefault(delegateReference, null);
        if (delegate != null) {
            delegate.doIt();
        }
    }

    public static class Singleton {
        private static DoItController instance;
        public static DoItController getInstance() {
            if (instance == null) {
                instance = new DoItController();
            }
            return instance;
        }
    }
}

class DoerDelegate implements Doer {
    @Override
    public void doIt() {
        System.out.println("Do something with: " + getClass().getSimpleName());
    }
}

class DoerDelegateA extends DoerDelegate {}
class DoerDelegateB extends DoerDelegate {}
class DoerDelegateComplex extends DoerDelegateA {
    @Override
    public void doIt() {
        System.out.println("Override standard method with: " + getClass().getSimpleName());
    }
}

or ...

public class MyClass {
    public static void main(String... args) {
        final B b = new B();
        b.doIt();
    }
}

interface Doer {
    void doIt();
}

class A implements Doer {
    @Override
    public void doIt() { System.out.println("DO IT"); }
}

class B implements Doer {
    final A aDelegate;
    public B() {
        aDelegate = new A();
    }

    @Override
    public void doIt() {
        // standard
        aDelegate.doIt();
        // your specific B stuff
        //...
    }
}

Upvotes: 0

Johannes Kuhn
Johannes Kuhn

Reputation: 15202

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.

Upvotes: 1

Related Questions