Search code examples
java

How to get a variable name in the caller class?


I wrote an extended JTextField class (MyClass) because JFormattedTextField didn't meet all my needs. Basically the class handles various format types, specified by a passed parameter.
 In a testing phase, all kinds of input strings are tested. In case such an input string is mistakenly rejected by my program, I would like to detect, in what instance/variable of MyClass this happened. This name together with the tested input string will then appear in a message, which the tester could forward to me for further inspection.
 I have found examples how to access a caller class and how to access variable names via reflection. But in order to use Class.getDeclaredField(String name) you have to know the name of the variable beforehand.

import java.lang.reflect.*;

public class MyApp {
  static MyClass myGloballyDeclaredClass;

  public static void main(String[] args) {
//    MyClass myClassWithFormatType1= new MyClass(1);
//    MyClass myClassWithFormatType2= new MyClass(2);
//    MyClass myClassWithFormatTypeX= new MyClass(x);
    myGloballyDeclaredClass= new MyClass(10);
  }
}


class MyClass {
  public MyClass(int flag) {
    boolean unexpectedInput= true;
    if (unexpectedInput) {
      Class<?> clazz= StackWalker
            .getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
            .getCallerClass();

//      Field f= clazz.getDeclaredField(String name);
//           The name is exactly what I want to detect.
//      Retrieving all fields is of no use, as there are many
//      instances of MyClass and I'm looking for an unknown
//      specific one.
//      Field[] fields = clazz.getDeclaredFields();
//      String variableName= ...; // This is the missing code.
      System.out.println("This instance of MyClass was called in "+
                         clazz.getName()+"\n"+
                         "and stored in variable ..."); // +variableName
    }
  }
}

I could, of course, pass the instance name as a string parameter to MyClass, but if the name could be elicited with code, it would not only be nicer, but also a help in future unforeseen requirements.

Edit
Matt and Old Dog Programmer pointed to an examination of the stack trace, which worked nice in the beginning. But when it comes closer to my Swing app, the creation line of MyTextField in MySwingApp is no more listed in the stack.
Here's the skeleton

import java.awt.*;
import javax.swing.*;

public class MySwingApp extends JFrame {
  MyTextField myTextField;

  public MySwingApp() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(300, 240);
    setLocationRelativeTo(null);

//    MyTextField myTextFieldWithFlag1= new MyTextField(1);
//    MyTextField myTextFieldWithFlag2= new MyTextField(2);
//    MyTextField myTextFieldWithFlagX= new MyTextField(x);
    myTextField= new MyTextField(10);
    add(myTextField, BorderLayout.NORTH);
    add(new JLabel("""
    <html>Enter any character string.<br>
    Letter "e" will be treated as erroneous and<br>
    launch a stack display.</html>"""));
    setVisible(true);
  }


  public static void main(String[] args) {
    EventQueue.invokeLater(MySwingApp::new);
  }

}

============================================

import javax.swing.*;
import javax.swing.text.*;

public class MyTextField extends JTextField {
  AbstractDocument doc = (AbstractDocument)this.getDocument();
  TheFilter filter;

  public MyTextField(int flag) {
    filter= new TheFilter(this);
    doc.setDocumentFilter(filter);
  }


  public void showStackTrace() {
    String stackTrace = "Stack Trace:\n";
//    StackTraceElement[] elements = Thread.currentThread().getStackTrace();
    StackTraceElement[] elements = new Throwable().getStackTrace();
    for(StackTraceElement ste : elements) {
      stackTrace+= ste.toString() + "\n";
    }
    System.out.println(stackTrace);
  }


  class TheFilter extends DocumentFilter {
    public TheFilter(MyTextField docOwner) {
    }

    public void insertString(DocumentFilter.FilterBypass fb, int offset,
        String str, AttributeSet attr) throws BadLocationException {
    }

    public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
                        throws BadLocationException {
    }

    public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
        String str, AttributeSet attrs) throws BadLocationException {
//    Letter "e" is deemed to be an erroneous input.
      if (str.equals("e")) {
        showStackTrace();
        return;
      }
      else
        fb.replace(offset, length, str, attrs);
    }
  }

}

Solution

  • What if the information about the instance was captured at the time it was created?

    package instanceid;
    
    import javax.swing.*;
    import javax.swing.text.*;
    
    public class MyTextField extends JTextField {
    
        AbstractDocument doc = (AbstractDocument) this.getDocument();
        TheFilter filter;
        private final String id;
        private static int serialNumber;
    
        public MyTextField(int flag) {
    
            filter = new TheFilter(this);
            doc.setDocumentFilter(filter);
            id = generateID(flag);
        }
    
        private static String generateID(int flag) {
            StackTraceElement[] theTrace = Thread.currentThread().getStackTrace();
            return "instance " + serialNumber++ + " of MyClass created from "
                    + theTrace[3].getClassName() + " in "
                    + theTrace[3].getFileName() + " @ "
                    + theTrace[3].getLineNumber()
                    + " with value " + flag;
        }
    

    Followed by the rest of the O/P's code.

    When System.out.println(id); is called in an instance method, such as replace , the output looks like this:

    instance 0 of MyClass created from instanceid.MySwingApp in MySwingApp.java @ 20 with value 10

    Of course, a public String getID () { return id; } could be added.

    Note: If this information is to be programmatically captured from the stack, it has to be done early. The stack has entries only for active methods. So, when the constructor or any other method returns to the caller, that information is removed from the stack.