/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package gnu.classpath;

/** This is a cheap stack browser.  Better would be something like
 * the Jikes RVM {@link StackBrowser} class.
 *
 * This is our interface to GNU Classpath.  We quote the official
 * Classpath Javadoc here, as part of clearly describing the interface.
 * Never the less, look at the source code of the GNU Class
 * (classpath/vm/reference/gnu/classpath/VMStackWalker.java) for the latest
 * description of what these methods should do.
 */

import org.jikesrvm.VM;
import org.jikesrvm.runtime.StackBrowser;
import org.jikesrvm.runtime.Entrypoints;

import org.jikesrvm.classloader.RVMType;


public final class VMStackWalker {

  /**                                                          n-
   * Walk up the stack and return the first non-null class loader.
   * If there aren't any non-null class loaders on the stack, return null.
   *
   * @return the first non-null classloader on stack or null.ge
   */
  public static ClassLoader firstNonNullClassLoader() {
    for (Class<?> type : getClassContext()) {
      ClassLoader loader = type.getClassLoader();
      if (loader != null)
        return loader;
    }
    return null;
  }

  /**
   * Classpath's Javadoc for this method says:
   * <blockquote>
   * Get a list of all the classes currently executing methods on the
   * Java stack. <code>getClassContext()[0]</code> is the class associated
   * with the currently executing method, i.e., the method that called
   * <code>VMStackWalker.getClassContext()</code> (possibly through
   * reflection). So you may need to pop off these stack frames from
   * the top of the stack:
   * <ul>
   * <li><code>VMStackWalker.getClassContext()</code>
   * <li><code>Method.invoke()</code>
   * </ul>
   *
   * @return an array of the declaring classes of each stack frame
   * </blockquote>
   */
  public static Class<?>[] getClassContext() {
    StackBrowser b = new StackBrowser();
    int frames = 0;
    VM.disableGC();

    b.init();
    b.up(); // skip VMStackWalker.getClassContext (this call)

    boolean reflected;  // Were we invoked by reflection?
    if (b.getMethod() == Entrypoints.java_lang_reflect_Method_invokeMethod){
      reflected = true;
      b.up();         // Skip Method.invoke, (if we were called by reflection)
    } else {
      reflected = false;
    }

    /* Count # of frames. */
    while(b.hasMoreFrames()) {
      frames++;
      b.up();
    }

    VM.enableGC();


    RVMType[] iclasses = new RVMType[ frames ];

    int i = 0;
    b = new StackBrowser();

    VM.disableGC();
    b.init();
    b.up(); // skip this method
    if (reflected)
      b.up();            // Skip Method.invoke if we were called by reflection

    while(b.hasMoreFrames()) {
      iclasses[i++] = b.getCurrentClass();
      b.up();
    }
    VM.enableGC();

    Class<?>[] classes = new Class[ frames ];
    for(int j = 0; j < iclasses.length; j++) {
      classes[j] = iclasses[j].getClassForType();
    }

    return classes;
  }

  /**
   * Classpath's Javadoc for this method is:
   * <blockquote>
   * Get the class associated with the method invoking the method
   * invoking this method, or <code>null</code> if the stack is not
   * that deep (e.g., invoked via JNI invocation API). This method
   * is an optimization for the expression <code>getClassContext()[1]</code>
   * and should return the same result.
   *
   * <p>
   * VM implementers are encouraged to provide a more efficient
   * version of this method.
   * </blockquote>
   */
  public static Class<?> getCallingClass() {
    return getCallingClass(1);  // Skip this method (getCallingClass())
  }

  public static Class<?> getCallingClass(int skip) {
    StackBrowser b = new StackBrowser();
    VM.disableGC();

    b.init();
    b.up(); // skip VMStackWalker.getCallingClass(int) (this call)
    while (skip-- > 0)          // Skip what the caller asked for.
      b.up();

    /* Skip Method.invoke, (if the caller was called by reflection) */
    if (b.getMethod() == Entrypoints.java_lang_reflect_Method_invokeMethod){
      b.up();
    }
    /* skip past another frame, whatever getClassContext()[0] would be. */
    if (!b.hasMoreFrames())
      return null;
    b.up();

    /* OK, we're there at getClassContext()[1] now.  Return it. */
    RVMType ret = b.getCurrentClass();
    VM.enableGC();

    return ret.getClassForType();
  }

  /**
   * Classpath's Javadoc for this method is:
   * <blockquote>
   * Get the class loader associated with the Class returned by
   * <code>getCallingClass()</code>, or <code>null</code> if no
   * such class exists or it is the boot loader. This method is an optimization
   * for the expression <code>getClassContext()[1].getClassLoader()</code>
   * and should return the same result.
   * </blockquote>
   */
  public static ClassLoader getCallingClassLoader() {
    Class<?> caller = getCallingClass(1); // skip getCallingClassLoader
    if (caller == null)
      return null;
    return caller.getClassLoader();
  }
}

