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