Back

package com.futureshocked.classloader;

import com.futureshocked.debug.StringUtil;

import java.lang.reflect.Method;

/**
 * <tt>ApplicationDebugger</tt> provides an easy way to intercept and modify
 *   classes for debugging purposes.<br><br>
 * To debug an application, a class name and a <tt>String</tt> array of
 *   arguments are passed in the constuctor, methods to intercept are specified
 *   using the addMethod() methods, and finally run() is called, which will
 *   invoke main() on the user specified class.
 */
public class ApplicationDebugger {
  /**
   * An instance of <tt>DebuggerClassLoader</tt>
   */
  protected ClassLoader cl;

  /**
   * Collection of <tt>InterceptedMethod</tt>
   */
  protected ClassTransforms ct;

  /**
   * The name of the class which contains the main() method to be invoked
   */
  protected String className;

  /**
   * The <tt>String</tt> array passed to the main method of <tt>className</tt>
   */
  protected String[] args;

  /**
   * The maximum recursive depth when
   *   <tt>StaticDebugger.printDebugInformation</tt> is called
   */
  protected int maxDebugDepth;

  /**
   * The default maximum recursive depth for
   *   <tt>StaticDebugger.printDebugInformation</tt>
   */
  protected final static int defaultDebugDepth = 10;

  /**
   * The maximum recursive depth when the limited recursion feature is used
   */
  protected int maxRecursionDepth;

  /**
   * The default maximum recursive depth for limited recursion
   */
  protected final static int defaultRecursionDepth = 10;

  /**
   * Prints debugging information when set to <tt>true</tt>
   */
  protected boolean verbose;

  /**
   * Constructor that will set <tt>maxDebugDepth</tt> and
   *   <tt>maxRecursiveDepth</tt> to their defaults.
   *
   * @param name Class name to be loaded, must contain a <tt>main</tt> method.
   * @param args Arguments to be passed to the <tt>main</tt> method.
   */
  public ApplicationDebugger(String name, String[] args) {
    this(name, args, ApplicationDebugger.defaultDebugDepth,
        ApplicationDebugger.defaultRecursionDepth);
  }

  /**
   * Constructor that will set <tt>maxDebugDepth</tt> and
   *   <tt>maxRecursiveDepth</tt> to user specified values.
   *
   * @param name Class name to be loaded, must contain a <tt>main</tt> method.
   * @param args Arguments to be passed to the <tt>main</tt> method.
   * @param maxDebugDepth Maximum depth for <tt>StaticDebugger</tt>.
   * @param maxRecursionDepth Maximum recusion depth for limited recursion.
   */
  public ApplicationDebugger(String name, String[] args, int maxDebugDepth,
      int maxRecursionDepth) {
    this.className = name;
    this.args = args;
    this.ct = new ClassTransforms();
    this.maxDebugDepth = maxDebugDepth;
    this.maxRecursionDepth = maxRecursionDepth;
  }

  /**
   * Creates a new classloader and invokes <tt>main</tt> on
   *   <tt>ApplicationDebugger.className</tt>.
   *
   * @throws Exception Most likely from the user specified application, though
   *   it might be a <tt>ClassNotFound</tt> exception.
   */
  public void run() throws Exception {
    if (cl == null)
      cl = new DebuggerClassLoader(DebuggerClassLoader.class.getClassLoader(),
          ct, verbose);
    Thread.currentThread().setContextClassLoader(cl);

    Class c = cl.loadClass(className);

    Method m = c.getMethod("main", new Class[]{args.getClass()});

    if (m != null) {
      if (verbose)
        System.out.println("DebugApplication: invoking " +
            StringUtil.getShortClassName(className) + ".main()");

      m.invoke(null, (Object) args);
    }
    else {
      if (verbose)
        System.out.println("DebugApplication: method main() not found in " +
            StringUtil.getShortClassName(className));

      throw new Exception("Method main() not found!");
    }
  }

  /**
   * Unloads the classloader - this will unload all classes loaded by the
   *   classloader, allowing any recompiled classes to be reloaded back into
   *   memory without restarting the application.
   */
  public void unloadClassLoader() {
    cl = null;
    System.gc();
    System.gc();
  }

  /**
   * Adds a method to be intercepted by <tt>DebuggerClassLoader</tt>. Since
   *   description is not specified, this {@link InterceptedMethod} will match
   *   all overloaded methods that match this method name.
   *
   * @param clazz The name of the class the method belongs to. Should include
   *   full package name too.
   * @param method The name of the method to be intercepted.
   * @param callStatic <tt>true</tt> to add a call to
   *   <tt>StaticDebugger.printDebugInformation</tt>.
   * @param limitRecursion <tt>true</tt> to limit the amount of times this
   *   method may call itself.
   * @return an {@link InterceptedMethod} that will match these values.
   */
  public InterceptedMethod addMethod(String clazz, String method,
      boolean callStatic, boolean limitRecursion) {
    return addMethod(clazz, method, null, callStatic, limitRecursion);
  }

  /**
   * Adds a method to be intercepted by <tt>DebuggerClassLoader</tt>.
   *   <tt>desc</tt> must be in the correct format, see the ASM documentation
   *   for details.
   *
   * @param clazz The name of the class the method belongs to. Should include
   *   full package name too.
   * @param method The name of the method to be intercepted.
   * @param desc The description of the method, per ASM specs.<br>For example,
   *   a method like '<i>public void
   *   testMethod(String name, int count)</i>' has a description of:<br>
   *   "(Ljava.lang.String;I)V".
   * @param callStatic <tt>true</tt> to add a call to
   *   <tt>StaticDebugger.printDebugInformation</tt>.
   * @param limitRecursion <tt>true</tt> to limit the amount of times this
   *   method may call itself.
   * @return an {@link InterceptedMethod} that will match these values.
   */
  public InterceptedMethod addMethod(String clazz, String method, String desc,
      boolean callStatic, boolean limitRecursion) {
    InterceptedMethod im = ct.addInterceptedMethod(clazz, method, desc);

    if (callStatic)
      im.setStaticDebuggerCall(maxDebugDepth);

    if (limitRecursion)
      im.setRecursionLimiter(maxRecursionDepth);

    return im;
  }

  /**
   * An easier way to add a method to be intercepted by
   *   <tt>DebuggerClassLoader</tt>. This allows the user to not worry about
   *   the description - the method declaration from the code may simply
   *   entered as <tt>code</tt>. One important thing to note is the full
   *   package name must be entered for all Objects.
   * @param code A method declaration as taken from the code, but with package
   *   names added for all Objects. <br>ie: "void testMethod(java.lang.String
   *   arg1, com.user.package.MyObject arg2)".
   * @param callStatic callStatic <tt>true</tt> to add a call to
   *   <tt>StaticDebugger.printDebugInformation</tt>.
   * @param limitRecursion <tt>true</tt> to limit the amount of times this
   *   method may call itself.
   * @return an {@link InterceptedMethod} that will match these values.
   */
  public InterceptedMethod addCodeStyleMethod(String code, boolean callStatic,
      boolean limitRecursion) {
    InterceptedMethod im = ct.addCodeStyleInterceptedMethod(code);

    if (callStatic)
      im.setStaticDebuggerCall(maxDebugDepth);

    if (limitRecursion)
      im.setRecursionLimiter(maxRecursionDepth);

    return im;
  }

  public int getMaxDebugDepth() {
    return maxDebugDepth;
  }

  public void setMaxDebugDepth(int depth) {
    this.maxDebugDepth = depth;
  }

  public int getMaxRecursionDepth() {
    return maxRecursionDepth;
  }

  public void setMaxRecursionDepth(int depth) {
    this.maxRecursionDepth = depth;
  }

  public void setVerbose(boolean verbose) {
    this.verbose = verbose;
  }

  public boolean getVerbose() {
    return verbose;
  }

  public static void main(String[] args) {
    if (args.length < 2) {
      System.out.println("Usage: java DebugApplication class_to_load " +
          "method_to_intercept (arguments for loaded class)");
      System.out.println("\nmethod_to_intercept should be in this format:");
      System.out.println("\"java.lang.Object com.test.TestClass.doTestMethod(" +
          "java.util.List var1)\"");
      System.exit(1);
    }
    int subArgsSize = (args.length - 2 > 0) ? (args.length - 2) : 0;
    String[] subArgs = new String[subArgsSize];
    System.arraycopy(args, 2, subArgs, 0, args.length - 2);

    ApplicationDebugger ad = new ApplicationDebugger(args[0], subArgs);

    ClassLoader cl = ad.getClass().getClassLoader();
    while (cl != null) {
      System.out.println(cl.toString());
      cl = cl.getParent();
    }
    ad.setVerbose(true);
    InterceptedMethod im = ad.addCodeStyleMethod(args[1], false, true);
    // To avoid using the default values for max recursion depth and max
    //   debug depth, they may be set directly with these methods.
    //im.setRecursionLimiter(3);
    //im.setStaticDebuggerCall(2);

    try {
      ad.run();
      // If you wish to reload the classes with different parameters, the
      //   following could be used.
      //ad.unloadClassLoader();
      //ad.setMaxDebugDepth(1);
      //ad.run();
    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}

Top