Home | FAQ | Contact me

Getting name of calling class or method

StackWalker is a Java 9 construct; it doesn't exist in Java 8. Here's a quick, but plausible replacement for just the functionality of getting the parent's classname, full classname or method name.

Line 32 below is key to understanding how to avoid any intermediate subclassing problems in order to get back what really called you.

StackParent.java:
package com.windofkeltia.stack_parent;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

/**
 * StackWalker is a Java 9 construct; it doesn't exist
 * in Java 8. Here's a quick, but plausible replacement for just
 * the functionality of getting the parent's classname, full
 * classname or method name.
 * @author Russell Bateman
 * @since October 2019
 */
public class StackParent
{
  private List< String > skipThese = new ArrayList<>();

  /**
   * Construct this utility to skip over stuff we happen not
   * to want to count in looking for a parent caller.
   * @param skipMe any number of classes to skip over by full package- and classname.
   */
  public StackParent( final String ... skipMe )
  {
    Collections.addAll( skipThese, skipMe );

    Class clazz = MethodHandles.lookup().lookupClass();
    skipThese.add( clazz.getName() );   // skip over this utility!
    skipThese.add( "java.lang.Thread" );
  }

  /**
   * See {@link #getParentMethod( String )} as if null is passed.
   */
  public String getParentMethod()
  {
    StackTraceElement parent = getParent();
    return( nonNull( parent ) ) ? parent.getMethodName() : null;
  }

  /**
   * See {@link #getParentFullClass( String )} as if null is passed.
   */
  public String getParentFullClass()
  {
    StackTraceElement parent = getParent();
    return( nonNull( parent ) ) ? parent.getClassName() : null;
  }

  /**
   * See {@link #getParentClass( String )} as if null is passed.
   */
  public String getParentClass()
  {
    String classPath = getParentFullClass();

    if( isNull( classPath ) )
      return null;

    String[] elements = classPath.split( "[.]" );
    return elements[ elements.length-1 ];
  }

  /**
   * Return the method name in the first class on the stack containing
   * the passed string. If only wanting the immediate caller no matter
   * what the name or package name, set MUST_BE_CONTAINED to null.
   * @param MUST_BE_CONTAINED string that helps reject names we're not looking for.
   * @return parent-method name.
   */
  public String getParentMethod( final String MUST_BE_CONTAINED )
  {
    StackTraceElement parent = getParent( MUST_BE_CONTAINED );
    return( nonNull( parent ) ) ? parent.getMethodName() : null;
  }

  /**
   * Return the full package name of the first class on the stack containing
   * the passed string. If only wanting the immediate caller no matter
   * what the name or package name, set MUST_BE_CONTAINED to null.
   * @param MUST_BE_CONTAINED string that helps reject names we're not looking for.
   * @return parent class' full name including package.
   */
  public String getParentFullClass( final String MUST_BE_CONTAINED )
  {
    StackTraceElement parent = getParent( MUST_BE_CONTAINED );
    return( nonNull( parent ) ) ? parent.getClassName() : null;
  }

  /**
   * Return the simple name of the first class on the stack containing
   * the passed string (in its package or simple name). If only wanting
   * the immediate caller no matter what the name or package name, set
   * MUST_BE_CONTAINED to null.
   * @param MUST_BE_CONTAINED string that helps reject names we're not looking for.
   * @return parent class' name alone devoid of package path.
   */
  public String getParentClass( final String MUST_BE_CONTAINED )
  {
    String classPath = getParentFullClass( MUST_BE_CONTAINED );

    if( isNull( classPath ) )
      return null;

    String[] elements = classPath.split( "[.]" );
    return elements[ elements.length-1 ];
  }

  /** (Heavy lifting...) */
  private StackTraceElement getParent()
  {
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

    for( StackTraceElement element : stackTraceElements )
    {
      String FULLCLASS = element.getClassName();

      if( skipThese.contains( FULLCLASS ) )
        continue;

      return element;
    }

    return null;
  }

  /** (Heavy lifting...) */
  private StackTraceElement getParent( final String MUST_BE_CONTAINED )
  {
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

    for( StackTraceElement element : stackTraceElements )
    {
      String FULLCLASS = element.getClassName();

      if( skipThese.contains( FULLCLASS ) )
        continue;

      // extra zeroing in: look for package/classname with 'MUST_BE_CONTAINED' in it...
      final String CLASSNAME = element.getClassName();

      if( CLASSNAME.contains( MUST_BE_CONTAINED ) )
        return element;
    }

    return null;
  }
}
StackParentTest.java:
package com.windofkeltia.stack_parent;

import org.junit.Test;

public class StackParentTest
{
  @Test
  public void test()
  {
    StackParent stackParent = new StackParent();
    System.out.println( "  Parent path:   " + stackParent.getParentFullClass() );
    System.out.println( "  Parent class:  " + stackParent.getParentClass() );
    System.out.println( "  Parent method: " + stackParent.getParentMethod() );
  }
}

Output:

  Parent path:   com.windofkeltia.stack_parent.StackParentTest
  Parent class:  StackParentTest
  Parent method: test