/*
  * JBoss, Home of Professional Open Source
  * Copyright 2005, JBoss Inc., and individual contributors as indicated
  * by the @authors tag. See the copyright.txt in the distribution for a
  * full listing of individual contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
package org.jboss.aop;

import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Map;

import org.jboss.util.collection.SoftValueHashMap;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 1.37 $
 */
public class AOPClassPool extends ClassPool
{
   protected AspectManager manager;
   //protected ClassLoader dcl;
   private WeakReference dcl;
   protected LoaderClassPath classPath;
   protected SoftValueHashMap softcache = new SoftValueHashMap();

   static 
   {
      ClassPool.doPruning = false;
      ClassPool.releaseUnmodifiedClassFile = false;
   }   
   
   public AOPClassPool(ClassLoader cl, ClassPool src, AspectManager manager)
   {
      super(src);
      this.manager = manager;
      setClassLoader(cl);
      if (cl != null)
      {
         classPath = new LoaderClassPath(cl);
         this.insertClassPath(classPath);
      }
      childFirstLookup = true;
   }

   protected AOPClassPool(ClassPool src, AspectManager manager)
   {
      this(null, src, manager);
   }
   
   public ClassLoader getClassLoader()
   {
      if (dcl==null)
      {
         return null;
      }
      else
      {
         return (ClassLoader)dcl.get();
      }
   }

   public void setClassLoader(ClassLoader loader)
   {
      if (loader == null)
      {
         this.dcl = null;
      }
      else
      {
         this.dcl = new WeakReference(loader);
      }
   }

   public void close()
   {
      this.removeClassPath(classPath);
      classPath.close();
      classes.clear();
      softcache.clear();
   }

   public synchronized void flushClass(String classname)
   {
      classes.remove(classname);
      softcache.remove(classname);
   }

   public synchronized void soften(CtClass clazz)
   {
      if (AspectManager.prune) clazz.prune();
      classes.remove(clazz.getName());
      softcache.put(clazz.getName(), clazz);
   }


   public boolean isUnloadedClassLoader()
   {
      return false;
   }

   protected CtClass getCached(String classname)
   {
      CtClass clazz = getCachedLocally(classname);
      if (clazz == null)
      {
         boolean isLocal = false; 
         
         if (getClassLoader() != 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 = getClassLoader().getResource(classResourceName) != null; 
         }
         
         if (!isLocal)
         {
            Map registeredCLs = manager.getRegisteredCLs();
            synchronized (registeredCLs)
            {
               Iterator it = registeredCLs.values().iterator();
               while (it.hasNext())
               {
                  AOPClassPool pool = (AOPClassPool) it.next();
                  if (pool.isUnloadedClassLoader())
                  {
                     AspectManager.instance().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;
   }
   protected void cacheCtClass(String classname, CtClass c, boolean dynamic)
   {
      if (dynamic)
      {
         super.cacheCtClass(classname, c, dynamic);
      }
      else
      {
         if (AspectManager.prune) c.prune();
         softcache.put(classname, c);
      }
   }

   public void lockInCache(CtClass c)
   {
      super.cacheCtClass(c.getName(), c, false);
   }

   protected CtClass getCachedLocally(String classname)
   {
      CtClass cached = (CtClass) classes.get(classname);
      if (cached != null) return cached;
      synchronized (softcache)
      {
         return (CtClass) softcache.get(classname);
      }
   }

   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;
   }


   public static AOPClassPool createAOPClassPool(ClassLoader cl, ClassPool src, AspectManager manager)
   {
      return AspectManager.classPoolFactory.create(cl, src, manager);
   }

   public static AOPClassPool createAOPClassPool(ClassPool src, AspectManager manager)
   {
      return AspectManager.classPoolFactory.create(src, manager);
   }

   public Class toClass(CtClass ct, ClassLoader loader) 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, getClassLoader());
   }

}
