/**
 * The repository maintains informations about class interdependencies, e.g., whether a class is a
 * sub-class of another. Delegates actual class loading to SyntheticRepository with current class
 * path by default.
 *
 * @see com.sun.org.apache.bcel.internal.util.Repository
 * @see com.sun.org.apache.bcel.internal.util.SyntheticRepository
 * @author <A HREF="mailto:[email protected]">M. Dahm</A>
 */
public abstract class Repository {
  private static com.sun.org.apache.bcel.internal.util.Repository _repository =
      SyntheticRepository.getInstance();

  /** @return currently used repository instance */
  public static com.sun.org.apache.bcel.internal.util.Repository getRepository() {
    return _repository;
  }

  /** Set repository instance to be used for class loading */
  public static void setRepository(com.sun.org.apache.bcel.internal.util.Repository rep) {
    _repository = rep;
  }

  /**
   * Lookup class somewhere found on your CLASSPATH, or whereever the repository instance looks for
   * it.
   *
   * @return class object for given fully qualified class name, or null if the class could not be
   *     found or parsed correctly
   */
  public static JavaClass lookupClass(String class_name) {
    try {
      JavaClass clazz = _repository.findClass(class_name);

      if (clazz == null) {
        return _repository.loadClass(class_name);
      } else {
        return clazz;
      }
    } catch (ClassNotFoundException ex) {
      return null;
    }
  }

  /**
   * Try to find class source via getResourceAsStream().
   *
   * @see Class
   * @return JavaClass object for given runtime class
   */
  public static JavaClass lookupClass(Class clazz) {
    try {
      return _repository.loadClass(clazz);
    } catch (ClassNotFoundException ex) {
      return null;
    }
  }

  /** @return class file object for given Java class. */
  public static ClassPath.ClassFile lookupClassFile(String class_name) {
    try {
      return ClassPath.SYSTEM_CLASS_PATH.getClassFile(class_name);
    } catch (IOException e) {
      return null;
    }
  }

  /** Clear the repository. */
  public static void clearCache() {
    _repository.clear();
  }

  /**
   * Add clazz to repository if there isn't an equally named class already in there.
   *
   * @return old entry in repository
   */
  public static JavaClass addClass(JavaClass clazz) {
    JavaClass old = _repository.findClass(clazz.getClassName());
    _repository.storeClass(clazz);
    return old;
  }

  /** Remove class with given (fully qualified) name from repository. */
  public static void removeClass(String clazz) {
    _repository.removeClass(_repository.findClass(clazz));
  }

  /** Remove given class from repository. */
  public static void removeClass(JavaClass clazz) {
    _repository.removeClass(clazz);
  }

  /**
   * @return list of super classes of clazz in ascending order, i.e., Object is always the last
   *     element
   */
  public static JavaClass[] getSuperClasses(JavaClass clazz) {
    return clazz.getSuperClasses();
  }

  /**
   * @return list of super classes of clazz in ascending order, i.e., Object is always the last
   *     element. return "null", if class cannot be found.
   */
  public static JavaClass[] getSuperClasses(String class_name) {
    JavaClass jc = lookupClass(class_name);
    return (jc == null ? null : getSuperClasses(jc));
  }

  /**
   * @return all interfaces implemented by class and its super classes and the interfaces that those
   *     interfaces extend, and so on. (Some people call this a transitive hull).
   */
  public static JavaClass[] getInterfaces(JavaClass clazz) {
    return clazz.getAllInterfaces();
  }

  /**
   * @return all interfaces implemented by class and its super classes and the interfaces that
   *     extend those interfaces, and so on
   */
  public static JavaClass[] getInterfaces(String class_name) {
    return getInterfaces(lookupClass(class_name));
  }

  /**
   * Equivalent to runtime "instanceof" operator.
   *
   * @return true, if clazz is an instance of super_class
   */
  public static boolean instanceOf(JavaClass clazz, JavaClass super_class) {
    return clazz.instanceOf(super_class);
  }

  /** @return true, if clazz is an instance of super_class */
  public static boolean instanceOf(String clazz, String super_class) {
    return instanceOf(lookupClass(clazz), lookupClass(super_class));
  }

  /** @return true, if clazz is an instance of super_class */
  public static boolean instanceOf(JavaClass clazz, String super_class) {
    return instanceOf(clazz, lookupClass(super_class));
  }

  /** @return true, if clazz is an instance of super_class */
  public static boolean instanceOf(String clazz, JavaClass super_class) {
    return instanceOf(lookupClass(clazz), super_class);
  }

  /** @return true, if clazz is an implementation of interface inter */
  public static boolean implementationOf(JavaClass clazz, JavaClass inter) {
    return clazz.implementationOf(inter);
  }

  /** @return true, if clazz is an implementation of interface inter */
  public static boolean implementationOf(String clazz, String inter) {
    return implementationOf(lookupClass(clazz), lookupClass(inter));
  }

  /** @return true, if clazz is an implementation of interface inter */
  public static boolean implementationOf(JavaClass clazz, String inter) {
    return implementationOf(clazz, lookupClass(inter));
  }

  /** @return true, if clazz is an implementation of interface inter */
  public static boolean implementationOf(String clazz, JavaClass inter) {
    return implementationOf(lookupClass(clazz), inter);
  }
}
/**
 * Drop in replacement for the standard class loader of the JVM. You can use it in conjunction with
 * the JavaWrapper to dynamically modify/create classes as they're requested.
 *
 * <p>This class loader recognizes special requests in a distinct format, i.e., when the name of the
 * requested class contains with "$$BCEL$$" it calls the createClass() method with that name
 * (everything bevor the $$BCEL$$ is considered to be the package name. You can subclass the class
 * loader and override that method. "Normal" classes class can be modified by overriding the
 * modifyClass() method which is called just before defineClass().
 *
 * <p>There may be a number of packages where you have to use the default class loader (which may
 * also be faster). You can define the set of packages where to use the system class loader in the
 * constructor. The default value contains "java.", "sun.", "javax."
 *
 * @version $Id: ClassLoader.java,v 1.1.2.1 2005/07/31 23:47:03 jeffsuttor Exp $
 * @author <A HREF="mailto:[email protected]">M. Dahm</A>
 * @see JavaWrapper
 * @see ClassPath
 */
public class ClassLoader extends java.lang.ClassLoader {
  private Hashtable classes = new Hashtable(); // Hashtable is synchronized thus thread-safe
  private String[] ignored_packages = {"java.", "javax.", "sun."};
  private Repository repository = SyntheticRepository.getInstance();
  private java.lang.ClassLoader deferTo = ClassLoader.getSystemClassLoader();

  public ClassLoader() {}

  public ClassLoader(java.lang.ClassLoader deferTo) {
    this.deferTo = deferTo;
    this.repository = new ClassLoaderRepository(deferTo);
  }

  /**
   * @param ignored_packages classes contained in these packages will be loaded with the system
   *     class loader
   */
  public ClassLoader(String[] ignored_packages) {
    addIgnoredPkgs(ignored_packages);
  }

  public ClassLoader(java.lang.ClassLoader deferTo, String[] ignored_packages) {
    this.deferTo = deferTo;
    this.repository = new ClassLoaderRepository(deferTo);

    addIgnoredPkgs(ignored_packages);
  }

  private void addIgnoredPkgs(String[] ignored_packages) {
    String[] new_p = new String[ignored_packages.length + this.ignored_packages.length];

    System.arraycopy(this.ignored_packages, 0, new_p, 0, this.ignored_packages.length);
    System.arraycopy(
        ignored_packages, 0, new_p, this.ignored_packages.length, ignored_packages.length);

    this.ignored_packages = new_p;
  }

  protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException {
    Class cl = null;

    /* First try: lookup hash table.
     */
    if ((cl = (Class) classes.get(class_name)) == null) {
      /* Second try: Load system class using system class loader. You better
       * don't mess around with them.
       */
      for (int i = 0; i < ignored_packages.length; i++) {
        if (class_name.startsWith(ignored_packages[i])) {
          cl = deferTo.loadClass(class_name);
          break;
        }
      }

      if (cl == null) {
        JavaClass clazz = null;

        /* Third try: Special request?
         */
        if (class_name.indexOf("$$BCEL$$") >= 0) clazz = createClass(class_name);
        else { // Fourth try: Load classes via repository
          if ((clazz = repository.loadClass(class_name)) != null) {
            clazz = modifyClass(clazz);
          } else throw new ClassNotFoundException(class_name);
        }

        if (clazz != null) {
          byte[] bytes = clazz.getBytes();
          cl = defineClass(class_name, bytes, 0, bytes.length);
        } else // Fourth try: Use default class loader
        cl = Class.forName(class_name);
      }

      if (resolve) resolveClass(cl);
    }

    classes.put(class_name, cl);

    return cl;
  }

  /**
   * Override this method if you want to alter a class before it gets actually loaded. Does nothing
   * by default.
   */
  protected JavaClass modifyClass(JavaClass clazz) {
    return clazz;
  }

  /**
   * Override this method to create you own classes on the fly. The name contains the special token
   * $$BCEL$$. Everything before that token is consddered to be a package name. You can encode you
   * own arguments into the subsequent string. You must regard however not to use any "illegal"
   * characters, i.e., characters that may not appear in a Java class name too<br>
   * The default implementation interprets the string as a encoded compressed Java class, unpacks
   * and decodes it with the Utility.decode() method, and parses the resulting byte array and
   * returns the resulting JavaClass object.
   *
   * @param class_name compressed byte code with "$$BCEL$$" in it
   */
  protected JavaClass createClass(String class_name) {
    int index = class_name.indexOf("$$BCEL$$");
    String real_name = class_name.substring(index + 8);

    JavaClass clazz = null;
    try {
      byte[] bytes = Utility.decode(real_name, true);
      ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");

      clazz = parser.parse();
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }

    // Adapt the class name to the passed value
    ConstantPool cp = clazz.getConstantPool();

    ConstantClass cl =
        (ConstantClass) cp.getConstant(clazz.getClassNameIndex(), Constants.CONSTANT_Class);
    ConstantUtf8 name = (ConstantUtf8) cp.getConstant(cl.getNameIndex(), Constants.CONSTANT_Utf8);
    name.setBytes(class_name.replace('.', '/'));

    return clazz;
  }
}