/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package javassist;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Enumeration;

import javassist.bytecode.ClassFile;
import javassist.bytecode.Descriptor;

/**
 * A container of <code>CtClass</code> objects.
 * A <code>CtClass</code> object must be obtained from this object.
 * If <code>get()</code> is called on this object,
 * it searches various sources represented by <code>ClassPath</code>
 * to find a class file and then it creates a <code>CtClass</code> object
 * representing that class file.  The created object is returned to the
 * caller.
 *
 * <p><b>Memory consumption memo:</b>
 *
 * <p><code>ClassPool</code> objects hold all the <code>CtClass</code>es
 * that have been created so that the consistency among modified classes
 * can be guaranteed.  Thus if a large number of <code>CtClass</code>es
 * are processed, the <code>ClassPool</code> will consume a huge amount
 * of memory.  To avoid this, a <code>ClassPool</code> object
 * should be recreated, for example, every hundred classes processed.
 * Note that <code>getDefault()</code> is a singleton factory.
 * Otherwise, <code>detach()</code> in <code>CtClass</code> should be used
 * to avoid huge memory consumption.
 *
 * <p><b><code>ClassPool</code> hierarchy:</b>
 *
 * <p><code>ClassPool</code>s can make a parent-child hierarchy as
 * <code>java.lang.ClassLoader</code>s.  If a <code>ClassPool</code> has
 * a parent pool, <code>get()</code> first asks the parent pool to find
 * a class file.  Only if the parent could not find the class file,
 * <code>get()</code> searches the <code>ClassPath</code>s of
 * the child <code>ClassPool</code>.  This search order is reversed if
 * <code>ClassPath.childFirstLookup</code> is <code>true</code>.
 *
 * @see javassist.CtClass
 * @see javassist.ClassPath
 */
public class ClassPool {
    // used by toClass().
    private static java.lang.reflect.Method defineClass1, defineClass2;
    private static java.lang.reflect.Method definePackage;

    static {
        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction(){
                public Object run() throws Exception{
                    Class cl = Class.forName("java.lang.ClassLoader");
                    defineClass1 = cl.getDeclaredMethod("defineClass",
                            new Class[] { String.class, byte[].class,
                                         int.class, int.class });

                    defineClass2 = cl.getDeclaredMethod("defineClass",
                           new Class[] { String.class, byte[].class,
                                 int.class, int.class, ProtectionDomain.class });

                    definePackage = cl.getDeclaredMethod("definePackage",
                            new Class[] { String.class, String.class, String.class,
                                          String.class, String.class, String.class,
                                          String.class, java.net.URL.class });
                    return null;
                }
            });
        }
        catch (PrivilegedActionException pae) {
            throw new RuntimeException("cannot initialize ClassPool", pae.getException());
        }
    }

    /**
     * Determines the search order.
     *
     * <p>If this field is true, <code>get()</code> first searches the
     * class path associated to this <code>ClassPool</code> and then
     * the class path associated with the parent <code>ClassPool</code>.
     * Otherwise, the class path associated with the parent is searched
     * first.
     *
     * <p>The default value is false.
     */
    public boolean childFirstLookup = false;

    /**
     * Turning the automatic pruning on/off.
     *
     * <p>If this field is true, <code>CtClass</code> objects are
     * automatically pruned by default when <code>toBytecode()</code> etc.
     * are called.  The automatic pruning can be turned on/off individually
     * for each <code>CtClass</code> object.
     *
     * <p>The initial value is false.
     *
     * @see CtClass#prune()
     * @see CtClass#stopPruning(boolean)
     * @see CtClass#detach()
     */
    public static boolean doPruning = false;

    private int compressCount;
    private static final int COMPRESS_THRESHOLD = 100;

    /* releaseUnmodifiedClassFile was introduced for avoiding a bug
       of JBoss AOP.  So the value should be true except for JBoss AOP.
     */

    /**
     * If true, unmodified and not-recently-used class files are
     * periodically released for saving memory.
     *
     * <p>The initial value is true.
     */
    public static boolean releaseUnmodifiedClassFile = true;

    protected ClassPoolTail source;
    protected ClassPool parent;
    protected Hashtable classes;        // should be synchronous

    /**
     * Table of registered cflow variables.
     */
    private Hashtable cflow = null;     // should be synchronous.

    private static final int INIT_HASH_SIZE = 191;

    private ArrayList importedPackages;

    /**
     * Creates a root class pool.  No parent class pool is specified.
     */
    public ClassPool() {
        this(null);
    }

    /**
     * Creates a root class pool.  If <code>useDefaultPath</code> is
     * true, <code>appendSystemPath()</code> is called.  Otherwise,
     * this constructor is equivalent to the constructor taking no
     * parameter.
     *
     * @param useDefaultPath    true if the system search path is
     *                          appended.
     */
    public ClassPool(boolean useDefaultPath) {
        this(null);
        if (useDefaultPath)
            appendSystemPath();
    }

    /**
     * Creates a class pool.
     *
     * @param parent    the parent of this class pool.  If this is a root
     *                  class pool, this parameter must be <code>null</code>.
     * @see javassist.ClassPool#getDefault()
     */
    public ClassPool(ClassPool parent) {
        this.classes = new Hashtable(INIT_HASH_SIZE);
        this.source = new ClassPoolTail();
        this.parent = parent;
        if (parent == null) {
            CtClass[] pt = CtClass.primitiveTypes;
            for (int i = 0; i < pt.length; ++i)
                classes.put(pt[i].getName(), pt[i]);
        }

        this.cflow = null;
        this.compressCount = 0;
        clearImportedPackages();
    }

    /**
     * Returns the default class pool.
     * The returned object is always identical since this method is
     * a singleton factory.
     *
     * <p>The default class pool searches the system search path,
     * which usually includes the platform library, extension
     * libraries, and the search path specified by the
     * <code>-classpath</code> option or the <code>CLASSPATH</code>
     * environment variable.
     *
     * <p>When this method is called for the first time, the default
     * class pool is created with the following code snippet:
     *
     * <pre>ClassPool cp = new ClassPool();
     * cp.appendSystemPath();
     * </pre>
     *
     * <p>If the default class pool cannot find any class files,
     * try <code>ClassClassPath</code> and <code>LoaderClassPath</code>.
     *
     * @see ClassClassPath
     * @see LoaderClassPath
     */
    public static synchronized ClassPool getDefault() {
        if (defaultPool == null) {
            defaultPool = new ClassPool(null);
            defaultPool.appendSystemPath();
        }

        return defaultPool;
    }

    private static ClassPool defaultPool = null;

    /**
     * Provide a hook so that subclasses can do their own
     * caching of classes.
     *
     * @see #cacheCtClass(String,CtClass,boolean)
     * @see #removeCached(String)
     */
    protected CtClass getCached(String classname) {
        return (CtClass)classes.get(classname);
    }

    /**
     * Provides a hook so that subclasses can do their own
     * caching of classes.
     *
     * @see #getCached(String)
     * @see #removeCached(String)
     */
    protected void cacheCtClass(String classname, CtClass c, boolean dynamic) {
        classes.put(classname, c);
    }

    /**
     * Provide a hook so that subclasses can do their own
     * caching of classes.
     *
     * @see #getCached(String)
     * @see #cacheCtClass(String,CtClass,boolean)
     */
    protected CtClass removeCached(String classname) {
        return (CtClass)classes.remove(classname);
    }

    /**
     * Returns the class search path.
     */
    public String toString() {
        return source.toString();
    }

    /**
     * This method is periodically invoked so that memory
     * footprint will be minimized.
     */
    void compress() {
        if (compressCount++ > COMPRESS_THRESHOLD) {
            compressCount = 0;
            Enumeration e = classes.elements();
            while (e.hasMoreElements())
                ((CtClass)e.nextElement()).compress();
        }
    }

    /**
     * Record a package name so that the Javassist compiler searches
     * the package to resolve a class name.
     * Don't record the <code>java.lang</code> package, which has
     * been implicitly recorded by default.
     *
     * <p>Since version 3.14, <code>packageName</code> can be a
     * fully-qualified class name.
     *
     * <p>Note that <code>get()</code> in <code>ClassPool</code> does
     * not search the recorded package.  Only the compiler searches it.
     *
     * @param packageName       the package name.
     *         It must not include the last '.' (dot).
     *         For example, "java.util" is valid but "java.util." is wrong.
     * @since 3.1
     */
    public void importPackage(String packageName) {
        importedPackages.add(packageName);
    }

    /**
     * Clear all the package names recorded by <code>importPackage()</code>.
     * The <code>java.lang</code> package is not removed.
     *
     * @see #importPackage(String)
     * @since 3.1
     */
    public void clearImportedPackages() {
        importedPackages = new ArrayList();
        importedPackages.add("java.lang");
    }

    /**
     * Returns all the package names recorded by <code>importPackage()</code>. 
     *
     * @see #importPackage(String)
     * @since 3.1
     */
    public Iterator getImportedPackages() {
        return importedPackages.iterator();
    }

    /**
     * Records a class name that never exists.
     * For example, a package name can be recorded by this method.
     * This would improve execution performance
     * since <code>get()</code> quickly throw an exception
     * without searching the class path at all
     * if the given name is an invalid name recorded by this method.
     * Note that searching the class path takes relatively long time.
     *
     * <p>The current implementation of this method performs nothing.
     *
     * @param name          an invalid class name (separeted by dots).
     * @deprecated
     */
    public void recordInvalidClassName(String name) {
        // source.recordInvalidClassName(name);
    }

    /**
     * Records the <code>$cflow</code> variable for the field specified
     * by <code>cname</code> and <code>fname</code>.
     *
     * @param name      variable name
     * @param cname     class name
     * @param fname     field name
     */
    void recordCflow(String name, String cname, String fname) {
        if (cflow == null)
            cflow = new Hashtable();

        cflow.put(name, new Object[] { cname, fname });
    }

    /**
     * Undocumented method.  Do not use; internal-use only.
     *
     * @param name      the name of <code>$cflow</code> variable
     */
    public Object[] lookupCflow(String name) {
        if (cflow == null)
            cflow = new Hashtable();

        return (Object[])cflow.get(name);
    }

    /**
     * Reads a class file and constructs a <code>CtClass</code>
     * object with a new name.
     * This method is useful if you want to generate a new class as a copy
     * of another class (except the class name).  For example,
     *
     * <pre>
     * getAndRename("Point", "Pair")
     * </pre>
     *
     * returns a <code>CtClass</code> object representing <code>Pair</code>
     * class.  The definition of <code>Pair</code> is the same as that of
     * <code>Point</code> class except the class name since <code>Pair</code>
     * is defined by reading <code>Point.class</code>.
     *
     * @param orgName   the original (fully-qualified) class name
     * @param newName   the new class name
     */
    public CtClass getAndRename(String orgName, String newName)
        throws NotFoundException
    {
        CtClass clazz = get0(orgName, false);
        if (clazz == null)
            throw new NotFoundException(orgName);

        if (clazz instanceof CtClassType)
            ((CtClassType)clazz).setClassPool(this);

        clazz.setName(newName);         // indirectly calls
                                        // classNameChanged() in this class
        return clazz;
    }

    /*
     * This method is invoked by CtClassType.setName().  It removes a
     * CtClass object from the hash table and inserts it with the new
     * name.  Don't delegate to the parent.
     */
    synchronized void classNameChanged(String oldname, CtClass clazz) {
        CtClass c = (CtClass)getCached(oldname);
        if (c == clazz)             // must check this equation.
            removeCached(oldname);  // see getAndRename().

        String newName = clazz.getName();
        checkNotFrozen(newName);
        cacheCtClass(newName, clazz, false);
    }

    /**
     * Reads a class file from the source and returns a reference
     * to the <code>CtClass</code>
     * object representing that class file.  If that class file has been
     * already read, this method returns a reference to the
     * <code>CtClass</code> created when that class file was read at the
     * first time.
     *
     * <p>If <code>classname</code> ends with "[]", then this method
     * returns a <code>CtClass</code> object for that array type.
     *
     * <p>To obtain an inner class, use "$" instead of "." for separating
     * the enclosing class name and the inner class name.
     *
     * @param classname         a fully-qualified class name.
     */
    public CtClass get(String classname) throws NotFoundException {
        CtClass clazz;
        if (classname == null)
            clazz = null;
        else
            clazz = get0(classname, true);

        if (clazz == null)
            throw new NotFoundException(classname);
        else {
            clazz.incGetCounter();
            return clazz;
        }
    }

    /**
     * Reads a class file from the source and returns a reference
     * to the <code>CtClass</code>
     * object representing that class file.
     * This method is equivalent to <code>get</code> except
     * that it returns <code>null</code> when a class file is
     * not found and it never throws an exception.
     *
     * @param classname     a fully-qualified class name.
     * @return a <code>CtClass</code> object or <code>null</code>.
     * @see #get(String)
     * @see #find(String)
     * @since 3.13
     */
    public CtClass getOrNull(String classname) {
        CtClass clazz = null;
        if (classname == null)
            clazz = null;
        else
            try {
                /* ClassPool.get0() never throws an exception
                   but its subclass may implement get0 that
                   may throw an exception.
                */
                clazz = get0(classname, true);
            }
            catch (NotFoundException e){}

        if (clazz != null)
            clazz.incGetCounter();

        return clazz;
    }

    /**
     * Returns a <code>CtClass</code> object with the given name.
     * This is almost equivalent to <code>get(String)</code> except
     * that classname can be an array-type "descriptor" (an encoded
     * type name) such as <code>[Ljava/lang/Object;</code>.
     *
     * <p>Using this method is not recommended; this method should be 
     * used only to obtain the <code>CtClass</code> object
     * with a name returned from <code>getClassInfo</code> in
     * <code>javassist.bytecode.ClassPool</code>.  <code>getClassInfo</code>
     * returns a fully-qualified class name but, if the class is an array
     * type, it returns a descriptor.
     *
     * @param classname         a fully-qualified class name or a descriptor
     *                          representing an array type.
     * @see #get(String)
     * @see javassist.bytecode.ConstPool#getClassInfo(int)
     * @see javassist.bytecode.Descriptor#toCtClass(String, ClassPool)
     * @since 3.8.1
     */
    public CtClass getCtClass(String classname) throws NotFoundException {
        if (classname.charAt(0) == '[')
            return Descriptor.toCtClass(classname, this);
        else
            return get(classname);
    }

    /**
     * @param useCache      false if the cached CtClass must be ignored.
     * @return null     if the class could not be found.
     */
    protected synchronized CtClass get0(String classname, boolean useCache)
        throws NotFoundException
    {
        CtClass clazz = null;
        if (useCache) {
            clazz = getCached(classname);
            if (clazz != null)
                return clazz;
        }

        if (!childFirstLookup && parent != null) {
            clazz = parent.get0(classname, useCache);
            if (clazz != null)
                return clazz;
        }

        clazz = createCtClass(classname, useCache);
        if (clazz != null) {
            // clazz.getName() != classname if classname is "[L<name>;".
            if (useCache)
                cacheCtClass(clazz.getName(), clazz, false);

            return clazz;
        }

        if (childFirstLookup && parent != null)
            clazz = parent.get0(classname, useCache);

        return clazz;
    }

    /**
     * Creates a CtClass object representing the specified class.
     * It first examines whether or not the corresponding class
     * file exists.  If yes, it creates a CtClass object.
     *
     * @return null if the class file could not be found.
     */
    protected CtClass createCtClass(String classname, boolean useCache) {
        // accept "[L<class name>;" as a class name. 
        if (classname.charAt(0) == '[')
            classname = Descriptor.toClassName(classname);

        if (classname.endsWith("[]")) {
            String base = classname.substring(0, classname.indexOf('['));
            if ((!useCache || getCached(base) == null) && find(base) == null)
                return null;
            else
                return new CtArray(classname, this);
        }
        else
            if (find(classname) == null)
                return null;
            else
                return new CtClassType(classname, this);
    }

    /**
     * Searches the class path to obtain the URL of the class file
     * specified by classname.  It is also used to determine whether
     * the class file exists.
     *
     * @param classname     a fully-qualified class name.
     * @return null if the class file could not be found.
     * @see CtClass#getURL()
     */
    public URL find(String classname) {
        return source.find(classname);
    }

    /*
     * Is invoked by CtClassType.setName() and methods in this class.
     * This method throws an exception if the class is already frozen or
     * if this class pool cannot edit the class since it is in a parent
     * class pool.
     *
     * @see checkNotExists(String)
     */
    void checkNotFrozen(String classname) throws RuntimeException {
        CtClass clazz = getCached(classname);
        if (clazz == null) {
            if (!childFirstLookup && parent != null) {
                try {
                    clazz = parent.get0(classname, true);
                }
                catch (NotFoundException e) {}
                if (clazz != null)
                    throw new RuntimeException(classname
                            + " is in a parent ClassPool.  Use the parent.");
            }
        }
        else
            if (clazz.isFrozen())
                throw new RuntimeException(classname
                                        + ": frozen class (cannot edit)");
    }

    /*
     * This method returns null if this or its parent class pool does
     * not contain a CtClass object with the class name.
     *
     * @see checkNotFrozen(String)
     */
    CtClass checkNotExists(String classname) {
        CtClass clazz = getCached(classname);
        if (clazz == null)
            if (!childFirstLookup && parent != null) {
                try {
                    clazz = parent.get0(classname, true);
                }
                catch (NotFoundException e) {}
            }

        return clazz;
    }

    /* for CtClassType.getClassFile2().  Don't delegate to the parent.
     */
    InputStream openClassfile(String classname) throws NotFoundException {
        return source.openClassfile(classname);
    }

    void writeClassfile(String classname, OutputStream out)
        throws NotFoundException, IOException, CannotCompileException
    {
        source.writeClassfile(classname, out);
    }

    /**
     * Reads class files from the source and returns an array of
     * <code>CtClass</code>
     * objects representing those class files.
     *
     * <p>If an element of <code>classnames</code> ends with "[]",
     * then this method
     * returns a <code>CtClass</code> object for that array type.
     *
     * @param classnames        an array of fully-qualified class name.
     */
    public CtClass[] get(String[] classnames) throws NotFoundException {
        if (classnames == null)
            return new CtClass[0];

        int num = classnames.length;
        CtClass[] result = new CtClass[num];
        for (int i = 0; i < num; ++i)
            result[i] = get(classnames[i]);

        return result;
    }

    /**
     * Reads a class file and obtains a compile-time method.
     *
     * @param classname         the class name
     * @param methodname        the method name
     * @see CtClass#getDeclaredMethod(String)
     */
    public CtMethod getMethod(String classname, String methodname)
        throws NotFoundException
    {
        CtClass c = get(classname);
        return c.getDeclaredMethod(methodname);
    }

    /**
     * Creates a new class (or interface) from the given class file.
     * If there already exists a class with the same name, the new class
     * overwrites that previous class.
     *
     * <p>This method is used for creating a <code>CtClass</code> object
     * directly from a class file.  The qualified class name is obtained
     * from the class file; you do not have to explicitly give the name.
     *
     * @param classfile class file.
     * @throws RuntimeException if there is a frozen class with the
     *                          the same name.
     * @see #makeClassIfNew(InputStream)
     * @see javassist.ByteArrayClassPath
     */
    public CtClass makeClass(InputStream classfile)
        throws IOException, RuntimeException
    {
        return makeClass(classfile, true);
    }

    /**
     * Creates a new class (or interface) from the given class file.
     * If there already exists a class with the same name, the new class
     * overwrites that previous class.
     *
     * <p>This method is used for creating a <code>CtClass</code> object
     * directly from a class file.  The qualified class name is obtained
     * from the class file; you do not have to explicitly give the name.
     *
     * @param classfile class file.
     * @param ifNotFrozen       throws a RuntimeException if this parameter is true
     *                          and there is a frozen class with the same name.
     * @see javassist.ByteArrayClassPath
     */
    public CtClass makeClass(InputStream classfile, boolean ifNotFrozen)
        throws IOException, RuntimeException
    {
        compress();
        classfile = new BufferedInputStream(classfile);
        CtClass clazz = new CtClassType(classfile, this);
        clazz.checkModify();
        String classname = clazz.getName();
        if (ifNotFrozen)
            checkNotFrozen(classname);

        cacheCtClass(classname, clazz, true);
        return clazz;
    }

    /**
     * Creates a new class (or interface) from the given class file.
     * If there already exists a class with the same name, the new class
     * overwrites that previous class.
     *
     * <p>This method is used for creating a <code>CtClass</code> object
     * directly from a class file.  The qualified class name is obtained
     * from the class file; you do not have to explicitly give the name.
     *
     * @param classfile         class file.
     * @throws RuntimeException if there is a frozen class with the
     *                          the same name.
     * @since 3.20
     */
    public CtClass makeClass(ClassFile classfile)
        throws RuntimeException
    {
        return makeClass(classfile, true);
    }

    /**
     * Creates a new class (or interface) from the given class file.
     * If there already exists a class with the same name, the new class
     * overwrites that previous class.
     *
     * <p>This method is used for creating a <code>CtClass</code> object
     * directly from a class file.  The qualified class name is obtained
     * from the class file; you do not have to explicitly give the name.
     *
     * @param classfile     class file.
     * @param ifNotFrozen       throws a RuntimeException if this parameter is true
     *                          and there is a frozen class with the same name.
     * @since 3.20
     */
    public CtClass makeClass(ClassFile classfile, boolean ifNotFrozen)
        throws RuntimeException
    {
        compress();
        CtClass clazz = new CtClassType(classfile, this);
        clazz.checkModify();
        String classname = clazz.getName();
        if (ifNotFrozen)
            checkNotFrozen(classname);

        cacheCtClass(classname, clazz, true);
        return clazz;
    }

    /**
     * Creates a new class (or interface) from the given class file.
     * If there already exists a class with the same name, this method
     * returns the existing class; a new class is never created from
     * the given class file.
     *
     * <p>This method is used for creating a <code>CtClass</code> object
     * directly from a class file.  The qualified class name is obtained
     * from the class file; you do not have to explicitly give the name.
     *
     * @param classfile             the class file.
     * @see #makeClass(InputStream)
     * @see javassist.ByteArrayClassPath
     * @since 3.9
     */
    public CtClass makeClassIfNew(InputStream classfile)
        throws IOException, RuntimeException
    {
        compress();
        classfile = new BufferedInputStream(classfile);
        CtClass clazz = new CtClassType(classfile, this);
        clazz.checkModify();
        String classname = clazz.getName();
        CtClass found = checkNotExists(classname);
        if (found != null)
            return found;
        else {
            cacheCtClass(classname, clazz, true);
            return clazz;
        }
    }

    /**
     * Creates a new public class.
     * If there already exists a class with the same name, the new class
     * overwrites that previous class.
     *
     * <p>If no constructor is explicitly added to the created new
     * class, Javassist generates constructors and adds it when
     * the class file is generated.  It generates a new constructor
     * for each constructor of the super class.  The new constructor
     * takes the same set of parameters and invokes the
     * corresponding constructor of the super class.  All the received
     * parameters are passed to it.
     *
     * @param classname                 a fully-qualified class name.
     * @throws RuntimeException         if the existing class is frozen.
     */
    public CtClass makeClass(String classname) throws RuntimeException {
        return makeClass(classname, null);
    }

    /**
     * Creates a new public class.
     * If there already exists a class/interface with the same name,
     * the new class overwrites that previous class.
     *
     * <p>If no constructor is explicitly added to the created new
     * class, Javassist generates constructors and adds it when
     * the class file is generated.  It generates a new constructor
     * for each constructor of the super class.  The new constructor
     * takes the same set of parameters and invokes the
     * corresponding constructor of the super class.  All the received
     * parameters are passed to it.
     *
     * @param classname  a fully-qualified class name.
     * @param superclass the super class.
     * @throws RuntimeException if the existing class is frozen.
     */
    public synchronized CtClass makeClass(String classname, CtClass superclass)
        throws RuntimeException
    {
        checkNotFrozen(classname);
        CtClass clazz = new CtNewClass(classname, this, false, superclass);
        cacheCtClass(classname, clazz, true);
        return clazz;
    }

    /**
     * Creates a new public nested class.
     * This method is called by {@link CtClassType#makeNestedClass()}.
     *
     * @param classname     a fully-qualified class name.
     * @return      the nested class.
     */
    synchronized CtClass makeNestedClass(String classname) {
        checkNotFrozen(classname);
        CtClass clazz = new CtNewNestedClass(classname, this, false, null);
        cacheCtClass(classname, clazz, true);
        return clazz;
    }

    /**
     * Creates a new public interface.
     * If there already exists a class/interface with the same name,
     * the new interface overwrites that previous one.
     *
     * @param name          a fully-qualified interface name.
     * @throws RuntimeException if the existing interface is frozen.
     */
    public CtClass makeInterface(String name) throws RuntimeException {
        return makeInterface(name, null);
    }

    /**
     * Creates a new public interface.
     * If there already exists a class/interface with the same name,
     * the new interface overwrites that previous one.
     *
     * @param name       a fully-qualified interface name.
     * @param superclass the super interface.
     * @throws RuntimeException if the existing interface is frozen.
     */
    public synchronized CtClass makeInterface(String name, CtClass superclass)
        throws RuntimeException
    {
        checkNotFrozen(name);
        CtClass clazz = new CtNewClass(name, this, true, superclass);
        cacheCtClass(name, clazz, true);
        return clazz;
    }

    /**
     * Creates a new annotation.
     * If there already exists a class/interface with the same name,
     * the new interface overwrites that previous one.
     *
     * @param name      a fully-qualified interface name.
     *                  Or null if the annotation has no super interface. 
     * @throws RuntimeException if the existing interface is frozen.
     * @since 3.19
     */
    public CtClass makeAnnotation(String name) throws RuntimeException {
        try {
            CtClass cc = makeInterface(name, get("java.lang.annotation.Annotation"));
            cc.setModifiers(cc.getModifiers() | Modifier.ANNOTATION);
            return cc;
        }
        catch (NotFoundException e) {
            // should never happen.
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * Appends the system search path to the end of the
     * search path.  The system search path
     * usually includes the platform library, extension
     * libraries, and the search path specified by the
     * <code>-classpath</code> option or the <code>CLASSPATH</code>
     * environment variable.
     *
     * @return the appended class path.
     */
    public ClassPath appendSystemPath() {
        return source.appendSystemPath();
    }

    /**
     * Insert a <code>ClassPath</code> object at the head of the
     * search path.
     *
     * @return the inserted class path.
     * @see javassist.ClassPath
     * @see javassist.URLClassPath
     * @see javassist.ByteArrayClassPath
     */
    public ClassPath insertClassPath(ClassPath cp) {
        return source.insertClassPath(cp);
    }

    /**
     * Appends a <code>ClassPath</code> object to the end of the
     * search path.
     *
     * @return the appended class path.
     * @see javassist.ClassPath
     * @see javassist.URLClassPath
     * @see javassist.ByteArrayClassPath
     */
    public ClassPath appendClassPath(ClassPath cp) {
        return source.appendClassPath(cp);
    }

    /**
     * Inserts a directory or a jar (or zip) file at the head of the
     * search path.
     *
     * @param pathname      the path name of the directory or jar file.
     *                      It must not end with a path separator ("/").
     *                      If the path name ends with "/*", then all the
     *                      jar files matching the path name are inserted.
     *
     * @return the inserted class path.
     * @throws NotFoundException    if the jar file is not found.
     */
    public ClassPath insertClassPath(String pathname)
        throws NotFoundException
    {
        return source.insertClassPath(pathname);
    }

    /**
     * Appends a directory or a jar (or zip) file to the end of the
     * search path.
     *
     * @param pathname the path name of the directory or jar file.
     *                 It must not end with a path separator ("/").
     *                      If the path name ends with "/*", then all the
     *                      jar files matching the path name are appended.
     *
     * @return the appended class path.
     * @throws NotFoundException if the jar file is not found.
     */
    public ClassPath appendClassPath(String pathname)
        throws NotFoundException
    {
        return source.appendClassPath(pathname);
    }

    /**
     * Detatches the <code>ClassPath</code> object from the search path.
     * The detached <code>ClassPath</code> object cannot be added
     * to the path again.
     */
    public void removeClassPath(ClassPath cp) {
        source.removeClassPath(cp);
    }

    /**
     * Appends directories and jar files for search.
     *
     * <p>The elements of the given path list must be separated by colons
     * in Unix or semi-colons in Windows.
     *
     * @param pathlist      a (semi)colon-separated list of
     *                      the path names of directories and jar files.
     *                      The directory name must not end with a path
     *                      separator ("/").
     * @throws NotFoundException if a jar file is not found.
     */
    public void appendPathList(String pathlist) throws NotFoundException {
        char sep = File.pathSeparatorChar;
        int i = 0;
        for (;;) {
            int j = pathlist.indexOf(sep, i);
            if (j < 0) {
                appendClassPath(pathlist.substring(i));
                break;
            }
            else {
                appendClassPath(pathlist.substring(i, j));
                i = j + 1;
            }
        }
    }

    /**
     * Converts the given class to a <code>java.lang.Class</code> object.
     * Once this method is called, further modifications are not
     * allowed any more.
     * To load the class, this method uses the context class loader
     * of the current thread.  It is obtained by calling
     * <code>getClassLoader()</code>.  
     * 
     * <p>This behavior can be changed by subclassing the pool and changing
     * the <code>getClassLoader()</code> method.
     * If the program is running on some application
     * server, the context class loader might be inappropriate to load the
     * class.
     *
     * <p>This method is provided for convenience.  If you need more
     * complex functionality, you should write your own class loader.
     *
     * <p><b>Warining:</b> A Class object returned by this method may not
     * work with a security manager or a signed jar file because a
     * protection domain is not specified.
     *
     * @see #toClass(CtClass, java.lang.ClassLoader, ProtectionDomain)
     * @see #getClassLoader()
     */
    public Class toClass(CtClass clazz) throws CannotCompileException {
        // Some subclasses of ClassPool may override toClass(CtClass,ClassLoader).
        // So we should call that method instead of toClass(.., ProtectionDomain).
        return toClass(clazz, getClassLoader()); 
    }

    /**
     * Get the classloader for <code>toClass()</code>, <code>getAnnotations()</code> in
     * <code>CtClass</code>, etc.
     * 
     * <p>The default is the context class loader.
     * 
     * @return the classloader for the pool
     * @see #toClass(CtClass)
     * @see CtClass#getAnnotations()
     */
    public ClassLoader getClassLoader() {
        return getContextClassLoader();
    }
    
    /**
     * Obtains a class loader that seems appropriate to look up a class
     * by name. 
     */
    static ClassLoader getContextClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * Converts the class to a <code>java.lang.Class</code> object.
     * Do not override this method any more at a subclass because
     * <code>toClass(CtClass)</code> never calls this method.
     *
     * <p><b>Warining:</b> A Class object returned by this method may not
     * work with a security manager or a signed jar file because a
     * protection domain is not specified.
     *
     * @deprecated      Replaced by {@link #toClass(CtClass,ClassLoader,ProtectionDomain)}.
     * A subclass of <code>ClassPool</code> that has been
     * overriding this method should be modified.  It should override
     * {@link #toClass(CtClass,ClassLoader,ProtectionDomain)}.
     */
    public Class toClass(CtClass ct, ClassLoader loader)
        throws CannotCompileException
    {
        return toClass(ct, loader, null);
    }

    /**
     * Converts the class to a <code>java.lang.Class</code> object.
     * Once this method is called, further modifications are not allowed
     * any more.
     *
     * <p>The class file represented by the given <code>CtClass</code> is
     * loaded by the given class loader to construct a
     * <code>java.lang.Class</code> object.  Since a private method
     * on the class loader is invoked through the reflection API,
     * the caller must have permissions to do that.
     *
     * <p>An easy way to obtain <code>ProtectionDomain</code> object is
     * to call <code>getProtectionDomain()</code>
     * in <code>java.lang.Class</code>.  It returns the domain that the
     * class belongs to.
     *
     * <p>This method is provided for convenience.  If you need more
     * complex functionality, you should write your own class loader.
     *
     * @param loader        the class loader used to load this class.
     *                      For example, the loader returned by
     *                      <code>getClassLoader()</code> can be used
     *                      for this parameter.
     * @param domain        the protection domain for the class.
     *                      If it is null, the default domain created
     *                      by <code>java.lang.ClassLoader</code> is used.
     *
     * @see #getClassLoader()
     * @since 3.3
     */
    public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain)
        throws CannotCompileException
    {
        try {
            byte[] b = ct.toBytecode();
            java.lang.reflect.Method method;
            Object[] args;
            if (domain == null) {
                method = defineClass1;
                args = new Object[] { ct.getName(), b, new Integer(0),
                                      new Integer(b.length)};
            }
            else {
                method = defineClass2;
                args = new Object[] { ct.getName(), b, new Integer(0),
                    new Integer(b.length), domain};
            }

            return (Class)toClass2(method, loader, args);
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (java.lang.reflect.InvocationTargetException e) {
            throw new CannotCompileException(e.getTargetException());
        }
        catch (Exception e) {
            throw new CannotCompileException(e);
        }
    }

    private static synchronized Object toClass2(Method method,
            ClassLoader loader, Object[] args)
        throws Exception
    {
        method.setAccessible(true);
        try {
            return method.invoke(loader, args);
        }
        finally {
            method.setAccessible(false);
        }
    }

    /**
     * Defines a new package.  If the package is already defined, this method
     * performs nothing.
     *
     * <p>You do not necessarily need to
     * call this method.  If this method is called, then  
     * <code>getPackage()</code> on the <code>Class</code> object returned 
     * by <code>toClass()</code> will return a non-null object.
     *
     * @param loader        the class loader passed to <code>toClass()</code> or
     *                      the default one obtained by <code>getClassLoader()</code>.
     * @param name          the package name.
     * @see #getClassLoader()
     * @see #toClass(CtClass)
     * @see CtClass#toClass()
     * @since 3.16
     */
    public void makePackage(ClassLoader loader, String name)
        throws CannotCompileException
    {
        Object[] args = new Object[] {
                name, null, null, null, null, null, null, null };
        Throwable t;
        try {
            toClass2(definePackage, loader, args);
            return;
        }
        catch (java.lang.reflect.InvocationTargetException e) {
            t = e.getTargetException();
            if (t == null)
                t = e;
            else if (t instanceof IllegalArgumentException) {
                // if the package is already defined, an IllegalArgumentException
                // is thrown.
                return;
            }
        }
        catch (Exception e) {
            t = e;
        }

        throw new CannotCompileException(t);
    }
}
