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 |