/*
 * 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.scopedpool;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.security.ProtectionDomain;
import java.util.Map;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
 * A scoped class pool.
 * 
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 * @version $Revision: 1.8 $
 */
public class ScopedClassPool extends ClassPool {
    protected ScopedClassPoolRepository repository;

    protected Reference<ClassLoader> classLoader;

    protected LoaderClassPath classPath;

    protected Map<String,CtClass> softcache = new SoftValueHashMap<String,CtClass>();
    
    boolean isBootstrapCl = true;

    static {
        ClassPool.doPruning = false;
        ClassPool.releaseUnmodifiedClassFile = false;
    }

    /**
     * Create a new ScopedClassPool.
     * 
     * @param cl
     *            the classloader
     * @param src
     *            the original class pool
     * @param repository
     *            the repository
     */
    protected ScopedClassPool(ClassLoader cl, ClassPool src,
            ScopedClassPoolRepository repository)
    {
       this(cl, src, repository, false);
    }
    
    /**
     * Create a new ScopedClassPool.
     * 
     * @param cl
     *            the classloader
     * @param src
     *            the original class pool
     * @param repository
     *            the repository
     * @param isTemp
     *            Whether this is a temporary pool used to resolve references
     */
    protected ScopedClassPool(ClassLoader cl, ClassPool src,
            ScopedClassPoolRepository repository, boolean isTemp)
    {
       super(src);
       this.repository = repository;
       this.classLoader = new WeakReference<ClassLoader>(cl);
       if (cl != null) {
           classPath = new LoaderClassPath(cl);
           this.insertClassPath(classPath);
       }
       childFirstLookup = true;
       if (!isTemp && cl == null)
       {
          isBootstrapCl = true;
       }
    }

    /**
     * Get the class loader
     * 
     * @return the class loader
     */
    public ClassLoader getClassLoader() {
       ClassLoader cl = getClassLoader0();
       if (cl == null && !isBootstrapCl)
       {
          throw new IllegalStateException(
                  "ClassLoader has been garbage collected");
       }
       return cl;
    }

    protected ClassLoader getClassLoader0() {
       return classLoader.get();
    }

    /**
     * Close the class pool
     */
    public void close() {
        this.removeClassPath(classPath);
        classes.clear();
        softcache.clear();
    }

    /**
     * Flush a class
     * 
     * @param classname
     *            the class to flush
     */
    public synchronized void flushClass(String classname) {
        classes.remove(classname);
        softcache.remove(classname);
    }

    /**
     * Soften a class
     * 
     * @param clazz
     *            the class
     */
    public synchronized void soften(CtClass clazz) {
        if (repository.isPrune())
            clazz.prune();
        classes.remove(clazz.getName());
        softcache.put(clazz.getName(), clazz);
    }

    /**
     * Whether the classloader is loader
     * 
     * @return false always
     */
    public boolean isUnloadedClassLoader() {
        return false;
    }

    /**
     * Get the cached class
     * 
     * @param classname
     *            the class name
     * @return the class
     */
    @Override
    protected CtClass getCached(String classname) {
        CtClass clazz = getCachedLocally(classname);
        if (clazz == null) {
            boolean isLocal = false;

            ClassLoader dcl = getClassLoader0();
            if (dcl != null) {
                final int lastIndex = classname.lastIndexOf('$');
                String classResourceName = null;
                if (lastIndex < 0) {
                    classResourceName = classname.replaceAll("[\\.]", "/")
                            + ".class";
                }
                else {
                    classResourceName = classname.substring(0, lastIndex)
                            .replaceAll("[\\.]", "/")
                            + classname.substring(lastIndex) + ".class";
                }

                isLocal = dcl.getResource(classResourceName) != null;
            }

            if (!isLocal) {
                Map<ClassLoader,ScopedClassPool> registeredCLs = repository.getRegisteredCLs();
                synchronized (registeredCLs) {
                    for (ScopedClassPool pool:registeredCLs.values()) {
                        if (pool.isUnloadedClassLoader()) {
                            repository.unregisterClassLoader(pool
                                    .getClassLoader());
                            continue;
                        }

                        clazz = pool.getCachedLocally(classname);
                        if (clazz != null) {
                            return clazz;
                        }
                    }
                }
            }
        }
        // *NOTE* NEED TO TEST WHEN SUPERCLASS IS IN ANOTHER UCL!!!!!!
        return clazz;
    }

    /**
     * Cache a class
     * 
     * @param classname
     *            the class name
     * @param c
     *            the ctClass
     * @param dynamic
     *            whether the class is dynamically generated
     */
    protected void cacheCtClass(String classname, CtClass c, boolean dynamic) {
        if (dynamic) {
            super.cacheCtClass(classname, c, dynamic);
        }
        else {
            if (repository.isPrune())
                c.prune();
            softcache.put(classname, c);
        }
    }

    /**
     * Lock a class into the cache
     * 
     * @param c
     *            the class
     */
    public void lockInCache(CtClass c) {
        super.cacheCtClass(c.getName(), c, false);
    }

    /**
     * Whether the class is cached in this pooled
     * 
     * @param classname
     *            the class name
     * @return the cached class
     */
    protected CtClass getCachedLocally(String classname) {
        CtClass cached = (CtClass)classes.get(classname);
        if (cached != null)
            return cached;
        synchronized (softcache) {
            return (CtClass)softcache.get(classname);
        }
    }

    /**
     * Get any local copy of the class
     * 
     * @param classname
     *            the class name
     * @return the class
     * @throws NotFoundException
     *             when the class is not found
     */
    public synchronized CtClass getLocally(String classname)
            throws NotFoundException {
        softcache.remove(classname);
        CtClass clazz = (CtClass)classes.get(classname);
        if (clazz == null) {
            clazz = createCtClass(classname, true);
            if (clazz == null)
                throw new NotFoundException(classname);
            super.cacheCtClass(classname, clazz, false);
        }

        return clazz;
    }

    /**
     * Convert a javassist class to a java class
     * 
     * @param ct
     *            the javassist class
     * @param loader
     *            the loader
     * @throws CannotCompileException
     *             for any error
     */
    public Class<?> toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain)
            throws CannotCompileException {
        // We need to pass up the classloader stored in this pool, as the
        // default implementation uses the Thread context cl.
        // In the case of JSP's in Tomcat,
        // org.apache.jasper.servlet.JasperLoader will be stored here, while
        // it's parent
        // org.jboss.web.tomcat.tc5.WebCtxLoader$ENCLoader is used as the Thread
        // context cl. The invocation class needs to
        // be generated in the JasperLoader classloader since in the case of
        // method invocations, the package name will be
        // the same as for the class generated from the jsp, i.e.
        // org.apache.jsp. For classes belonging to org.apache.jsp,
        // JasperLoader does NOT delegate to its parent if it cannot find them.
        lockInCache(ct);
        return super.toClass(ct, getClassLoader0(), domain);
    }
}
