/*
 * xtc - The eXTensible Compiler
 * Copyright (C) 2007 Robert Grimm
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */
package xtc.tree;

import java.io.PrintStream;
import java.io.PrintWriter;

import java.util.ArrayList;
import java.util.List;

/**
 * The superclass of exceptions signaled during visitor dispatch.
 *
 * @author Robert Grimm
 * @version $Revision: 1.2 $
 */
public class TraversalException extends RuntimeException {

  /**
   * Create a new traversal exception with the specified detail
   * message.
   *
   * @param message The detail message.
   */
  public TraversalException(String message) {
    super(message);
  }
  
  /**
   * Create a new traversal exception with the specified detail
   * message and cause.
   *
   * @param message The detail message.
   * @param cause The cause.
   */
  public TraversalException(String message, Throwable cause) {
    super(message, cause);
  }

  public Throwable getCause() {
    Throwable t = super.getCause();
    return null == t ? t : clean(t);
  }

  public void printStackTrace(PrintStream s) {
    clean(this);
    super.printStackTrace(s);
  }

  public void printStackTrace(PrintWriter s) {
    clean(this);
    super.printStackTrace(s);
  }

  /**
   * Clean the specified throwable's stack trace.  This method removes
   * any evidence of dynamic visitor dispatch from stack traces.
   *
   * @param t The throwable.
   * @return The throwable.
   */
  private static <T extends Throwable> T clean(T t) {
    StackTraceElement oldTrace[] = t.getStackTrace();

    int size = 0;
    for (StackTraceElement e : oldTrace) {
      if (isClean(e)) size++;
    }

    if (oldTrace.length == size) {
      if (null != t.getCause()) clean(t.getCause());
      return t;
    }

    List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>(size);
    for (StackTraceElement e : oldTrace) {
      if (isClean(e)) newTrace.add(e);
    }
    t.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()]));

    if (null != t.getCause()) clean(t.getCause());
    return t;
  }

  /**
   * Determine whether the specified stack trace element is clean,
   * i.e., does not refer to {@link Visitor#dispatch(Node)} or Java
   * reflection.
   *
   * @param e The stack trace element.
   * @return <code>true</code> if the element is clean.
   */
  private static boolean isClean(StackTraceElement e) {
    String klass  = e.getClassName();
    String method = e.getMethodName();

    return (! ((method.equals("dispatch") &&
                klass.equals("xtc.tree.Visitor")) ||
               (method.startsWith("invoke") &&
                (klass.equals("java.lang.reflect.Method") ||
                 klass.startsWith("sun.reflect.")))));
  }

}
