Back
package com.futureshocked.classloader;
import com.futureshocked.debug.StringUtil;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.IOException;
import java.security.CodeSource;
import java.security.ProtectionDomain;
/**
* <tt>ClassLoader</tt> that allows for easy debugging of classes.
*/
public class DebuggerClassLoader extends ClassLoader {
/**
* The parent <tt>ClassLoader</tt> for this object - very important!
*/
protected final ClassLoader cl;
/**
* {@link ClassTransforms} object that contains all {@link InterceptedMethod}
* objects.
*/
protected ClassTransforms ct;
/**
* <tt>true</tt> if debugging information should be printed.
*/
protected boolean verbose;
/**
* Simple constructor that makes an empty {@link ClassTransforms} and sets
* verbose to false.
* @param cl The parent <tt>ClassLoader</tt> for this object - do not set
* to null!
*/
public DebuggerClassLoader(ClassLoader cl) {
this(cl, null, false);
}
/**
* Full constructor that allows all instance variables to be set.
* @param cl The parent <tt>ClassLoader</tt> for this object - do not set
* to null!
* @param ct {@link ClassTransforms} that holds all {@link InterceptedMethod}
* objects
* @param verbose <tt>true</tt> if debugging information should be displayed.
*/
public DebuggerClassLoader(ClassLoader cl, ClassTransforms ct,
boolean verbose) {
super();
this.cl = cl;
this.verbose = verbose;
if (ct != null)
this.ct = ct;
else
ct = new ClassTransforms();
}
/**
* Loads the requested class if it's not currently loaded, or returns the
* already loaded instance if appropriate.<br>
* This method declaration is Java 1.5 specific...
*
* @param name The name of the class to be loaded.
* @param resolve ignored currently
* @return <tt>Class</tt> object representing requested class.
* @throws ClassNotFoundException
*/
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class c = findLoadedClass(name);
// make sure it wasn't already loaded by the bootstrap loader
if (c == null) {
try {
Class parentVersion = getParent().loadClass(name);
// checks to make sure we only modify java system classes if the user
// specifically requests it
if ((parentVersion.getClassLoader() != getParent()) &&
(!ct.modifyClass(name)))
c = parentVersion;
} catch (Exception e) {} //ignore any exceptions here
}
// if null, this instance needs to define() the class
if (c == null) {
if (verbose)
System.out.println("DebuggerClassLoader: loading " +
StringUtil.getShortClassName(name));
c = defineClass(name, true);
}
// if still null, class was not found
if (c == null)
throw new ClassNotFoundException();
return c;
}
/**
* Defines a class, after using {@link DebuggerClassVisitor} if appropriate.
* @param name The name of the class to load and define.
* @param modify <tt>true</tt> if we are going to transform this class.
* @return <tt>Class</tt> object representing the loaded class.
* @throws ClassNotFoundException
*/
private Class defineClass(String name, boolean modify)
throws ClassNotFoundException {
ClassReader reader = null;
try {
reader = new ClassReader(
getResourceAsStream(name.replace(".", "/") + ".class"));
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
ClassWriter writer = new ClassWriter(true);
ClassVisitor visitor = writer;
if (modify)
visitor = new DebuggerClassVisitor(writer, name, ct, verbose);
reader.accept(visitor, false);
byte[] bytecode = writer.toByteArray();
// Use reflection hack if the user wants to modify a java system class.
// See useSystemDefine() declaration for warnings about this.
if (name.startsWith("java"))
return useSystemDefine(name, bytecode);
else
return defineClass(name, bytecode, 0, bytecode.length);
}
/**
* A hack to use reflection to modify java system classes.<br><br>
*
* This is almost certainly a <B>BAD IDEA</B>, and should be used
* with caution. This is possibly Sun JVM specific, and might not even work
* on old versions of Sun's JVM.
* @param className Name of class to be loaded.
* @param bytecode The bytecode representing the class.
* @return <tt>Class</tt> object that represents the loaded class.
*/
private Class useSystemDefine(String className, byte[] bytecode) {
Class clazz = null;
try {
ClassLoader loader = ClassLoader.getSystemClassLoader();
ProtectionDomain pd = null;
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod("getDefaultDomain", new Class[] {});
// protected method invocaton
method.setAccessible(true);
try {
Object[] args = new Object[] {};
pd = (ProtectionDomain) method.invoke(loader, args);
} finally {
method.setAccessible(false);
}
CodeSource cs = pd.getCodeSource();
String source = null;
if (cs != null && cs.getLocation() != null) {
source = cs.getLocation().toString();
}
// Will call native method defineClass1(), thereby defining this class
// with the system classloader.
method = cls.getDeclaredMethod("defineClass1", new Class[] {
String.class, byte[].class, int.class, int.class,
ProtectionDomain.class, String.class});
method.setAccessible(true);
try {
Object[] args = new Object[] {className, bytecode, new Integer(0),
new Integer(bytecode.length), pd, source};
clazz = (Class) method.invoke(loader, args);
} finally {
method.setAccessible(false);
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
return clazz;
}
}
Top |