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

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.aop.AspectManager;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.scopedpool.ScopedClassPool;
import javassist.scopedpool.ScopedClassPoolRepository;

/**
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 79579 $
 */
public class AOPClassPool extends ScopedClassPool
{
   /** Classnames of classes that will be created - we do not want to look for these in other pools */
   protected ConcurrentHashMap<String, String> generatedClasses = new ConcurrentHashMap<String, String>();

   protected ConcurrentHashMap<String, Boolean> localResources = new ConcurrentHashMap<String, Boolean>();

   /** Classnames of classes that have been loaded, but were not woven */
   protected ConcurrentHashMap<String, Boolean> loadedButNotWovenClasses = new ConcurrentHashMap<String, Boolean>();

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

   public AOPClassPool(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository)
   {
      this(cl, src, repository, false);
   }

   protected AOPClassPool(ClassPool src, ScopedClassPoolRepository repository)
   {
      this(null, src, repository, true);
   }

   private AOPClassPool(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository, boolean isTemp)
   {
      super(cl, src, repository, isTemp);
   }

   public void setClassLoader(ClassLoader cl)
   {
      classLoader = new WeakReference<ClassLoader>(cl);
   }

   public void registerGeneratedClass(String className)
   {
      generatedClasses.put(className, className);
   }

   public void close()
   {
      super.close();
      AOPClassPoolRepository.getInstance().perfomUnregisterClassLoader(getClassLoader());
   }

   public CtClass getCached(String classname)
   {
      CtClass clazz = getCachedLocally(classname);
      if (clazz == null)
      {
         boolean isLocal = false;

         ClassLoader cl = getClassLoader0();

         if (cl != null)
         {
            isLocal = isLocalResource(classname);
         }

         if (!isLocal)
         {
            Object o = generatedClasses.get(classname);
            if (o == null)
            {
               Map<ClassLoader, ClassPool> registeredCLs = AspectManager.getRegisteredCLs();
               synchronized (registeredCLs)
               {
                  for(ClassPool pl : AspectManager.getRegisteredCLs().values())
                  {
                     AOPClassPool pool = (AOPClassPool) pl;
                     if (pool.isUnloadedClassLoader())
                     {
                        AspectManager.instance().unregisterClassLoader(pool.getClassLoader());
                        continue;
                     }

                     //Do not check classpools for scoped classloaders
                     if (!pool.includeInGlobalSearch())
                     {
                        continue;
                     }

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

   protected boolean includeInGlobalSearch()
   {
      return true;
   }

   protected String getResourceName(String classname)
   {
      final int lastIndex = classname.lastIndexOf('$');
      if (lastIndex < 0)
      {
         return classname.replaceAll("[\\.]", "/") + ".class";
      }
      else
      {
         return classname.substring(0, lastIndex).replaceAll("[\\.]", "/") + classname.substring(lastIndex) + ".class";
      }
   }

   protected boolean isLocalResource(String resourceName)
   {
      String classResourceName = getResourceName(resourceName);
      Boolean isLocal = localResources.get(classResourceName);
      if (isLocal != null)
      {
         return isLocal.booleanValue();
      }
      boolean localResource = isLocalClassLoaderResource(classResourceName);
      localResources.put(classResourceName, localResource ? Boolean.TRUE : Boolean.FALSE);
      return localResource;
   }

   public boolean isLocalClassLoaderResource(String classResourceName)
   {
      return getClassLoader().getResource(classResourceName) != null;
   }
   
   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);
         lockInCache(clazz);//Avoid use of the softclasscache
      }

      return clazz;
   }

   public void setClassLoadedButNotWoven(String classname)
   {
      loadedButNotWovenClasses.put(classname, Boolean.TRUE);
   }

   public boolean isClassLoadedButNotWoven(String classname)
   {
      return loadedButNotWovenClasses.get(classname) == Boolean.TRUE;
   }

   public static AOPClassPool createAOPClassPool(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository)
   {
      return (AOPClassPool)AspectManager.getClassPoolFactory().create(cl, src, repository);
   }

   public static AOPClassPool createAOPClassPool(ClassPool src, ScopedClassPoolRepository repository)
   {
      return (AOPClassPool)AspectManager.getClassPoolFactory().create(src, repository);
   }

   public String toString()
   {
      ClassLoader cl = null;
      try
      {
         cl = getClassLoader();
      }
      catch(IllegalStateException ignore)
      {
      }
      return this.getClass().getName() + "@" + System.identityHashCode(this) + " " + super.toString() + " - dcl:" + cl;
   }

   public CtClass internalGet0(String classname, boolean useCache) throws NotFoundException
   {
      return super.get0(classname, useCache);
   }
   
   
}
