/*
 * JBoss, the OpenSource J2EE WebOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.mx.loading;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.List;
import java.net.URL;

import org.jboss.logging.Logger;
import org.jboss.mx.loading.ClassLoadingTask.ThreadTask;


/** A utility class used by the UnifiedClassLoader3 to manage the thread based
 * class loading tasks.
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.1.2.6 $
 */
public class LoadMgr3
{
   private static Logger log = Logger.getLogger(LoadMgr3.class);
   /** Used as a synchronization monitor during the setup/teardown of the
      thread owning a UCL.loadClass lock
    */
   private static Object registrationLock = new Object();

   /** A Map<UnifiedClassLoader3, Thread> of the active loadClass UCL3/threads.
    * This must be accessed under the registrationLock monitor.
    */
   private static HashMap loadClassThreads = new HashMap();
   /** A Map<Thread, LinkedList<ThreadTask> > of the class loading tasks
    * associated with a thread
    */
   private static Map loadTasksByThread = Collections.synchronizedMap(new WeakHashMap());

   /** A UCL and its relative ordering with respect to the class loading.
    * The UCL with the lowest order to load a class is the UCL that will
    * populate the repository cache and be assigned as the UCL.loadClass
    * return value.
    */
   public static class PkgClassLoader
   {
      public final UnifiedClassLoader3 ucl;
      public final int order;

      public PkgClassLoader(UnifiedClassLoader3 ucl)
      {
         this(ucl, Integer.MAX_VALUE);
      }
      public PkgClassLoader(UnifiedClassLoader3 ucl, int order)
      {
         this.ucl = ucl;
         this.order = order;
      }
   }

   /** Register that a thread owns the UCL3.loadClass monitor. This is called
    * from within UCL3.loadClass(String,boolean) and this method creates
    * entries in the loadClassThreads and loadTasksByThread maps.
    */
   public static void registerLoaderThread(UnifiedClassLoader3 ucl, Thread t)
   {
      synchronized( registrationLock )
      {
         Object prevThread = loadClassThreads.put(ucl, t);
         if( log.isTraceEnabled() )
            log.trace("registerLoaderThread, ucl="+ucl+", t="+t+", prevT="+prevThread);

         synchronized( loadTasksByThread )
         {
            List taskList = (List) loadTasksByThread.get(t);
            if( taskList == null )
            {
               taskList = Collections.synchronizedList(new LinkedList());
               loadTasksByThread.put(t, taskList);
               if( log.isTraceEnabled() )
                  log.trace("created new task list");
            }
         }
         registrationLock.notifyAll();
      }
   }

   /** Initiate the class loading task. This is called by UCL3.loadClass to
    * initiate the process of loading the requested class. This first attempts
    * to load the class from the repository cache, and then the class loaders
    * in the repsository. If the package of the class is found in the repository
    * then one or more ThreadTask are created to complete the ClassLoadingTask.
    * The ThreadTask are assigned to the threads that own the associated UCL3
    * monitor. If no class loader serves the class package, then the requesting
    * class loader is asked if it can load the class.
    *
    * @return true if the class could be loaded from the cache or requesting
    * UCL3, false to indicate the calling thread must process the
    * tasks assigned to it until the ClassLoadingTask state is FINISHED
    * @exception ClassNotFoundException if there is no chance the class can
    * be loaded from the current repository class loaders.
    */
   public static boolean beginLoadTask(ClassLoadingTask task,
      UnifiedLoaderRepository3 repository)
      throws ClassNotFoundException
   {
      boolean trace = log.isTraceEnabled();
      if( trace )
         log.trace("Begin beginLoadTask, task="+task);

      // Try the cache before anything else.
      Class cls = repository.loadClassFromCache(task.classname);
      if( cls != null )
      {
         task.loadedClass = cls;
         task.state = ClassLoadingTask.FINISHED;
         if( trace )
            log.trace("End beginLoadTask, loadClassFromCache, classname: "+task.classname);
         return true;
      }

      // Next get the set of class loaders from the packages map
      HashSet pkgSet = repository.getPackageClassLoaders(task.classname);
      if( pkgSet == null || pkgSet.size() == 0 )
      {
         /* If there are no class loaders in the repository capable of handling
         the request ask the class loader itself in the event that its parent(s)
         can load the class.
         */
         try
         {
            cls = repository.loadClassFromClassLoader(task.classname, false,
               task.requestingClassLoader);
         }
         catch(LinkageError e)
         {
            if( trace )
               log.trace("End beginLoadTask, LinkageError for task: "+task, e);
            throw e;
         }
         if( cls != null )
         {
            task.loadedClass = cls;
            task.state = ClassLoadingTask.FINISHED;
            if( trace )
               log.trace("End beginLoadTask, loadClassFromClassLoader");
            return true;
         }

         // Else, fail the load
         if( trace )
            log.trace("End beginLoadTask, ClassNotFoundException");
         String msg = "No ClassLoaders found for: "+task.classname;
         throw new ClassNotFoundException(msg);
      }

      /* A class loading task for each ClassLoader is needed. There can be
         multiple class loaders for a pkg due to the pkg being spread out over
         multiple jars, or duplicate classes due to versioning/patches, or
         just bad packaging.

         In the case of a non-scoped deployment of multiple classes which
         will provide a PkgClassLoader to define the ordering, we simply
         choose an ordering based on the order the UCL3s were added to the
         repository. At most one of the candidate UCL3s will load the class
         in order to avoid ClassCastExceptions or LinkageErrors due to the
         strong Java type system/security model.

         TODO: A simple ordering mechanism exists, but this probably needs
         to be augmented.
       */
      Iterator iter = pkgSet.iterator();
      UnifiedClassLoader3 theUCL = null;
      int order = Integer.MAX_VALUE;
      while( iter.hasNext() )
      {
         Object next = iter.next();
         int uclOrder;
         UnifiedClassLoader3 ucl;
         // This may be either a PkgClassLoader or a UCL3
         if( next instanceof UnifiedClassLoader3 )
         {
            ucl = (UnifiedClassLoader3) next;
            uclOrder = ucl.getAddedOrder();
         }
         else
         {
            PkgClassLoader pkgUcl = (PkgClassLoader) next;
            ucl = pkgUcl.ucl;
            uclOrder = pkgUcl.order;
         }

         // Validate that the ucl has the class as a resource
         String classRsrcName = task.classname.replace('.', '/') + ".class";
         URL url = ucl.getResourceLocally(classRsrcName);
         if( url != null && uclOrder < order )
         {
            if( trace && theUCL != null )
               log.trace("Replacing UCL: "+theUCL+" with UCL:"+ucl);
            theUCL = ucl;
            order = uclOrder;
         }
      }
      if( theUCL == null )
      {
         /* If there are no class loaders in the repository capable of handling
         the request ask the class loader itself in the event that its parent(s)
         can load the class.
         */
         try
         {
            cls = repository.loadClassFromClassLoader(task.classname, false,
               task.requestingClassLoader);
         }
         catch(LinkageError e)
         {
            if( trace )
               log.trace("End beginLoadTask, LinkageError for task: "+task, e);
            throw e;
         }
         if( cls != null )
         {
            task.loadedClass = cls;
            task.state = ClassLoadingTask.FINISHED;
            if( trace )
               log.trace("End beginLoadTask, loadClassFromClassLoader");
            return true;
         }

         // Else, fail the load
         if( trace )
            log.trace("End beginLoadTask, ClassNotFoundException");
         String msg = "No ClassLoaders found for: "+task.classname;
         throw new ClassNotFoundException(msg);
      }

      scheduleTask(task, theUCL, order, false, trace);
      task.state = ClassLoadingTask.FOUND_CLASS_LOADER;
      if( trace )
         log.trace("End beginLoadTask, task="+task);

      return false;
   }

   /** Called by threads owning a UCL3.loadLock from within UCL3.loadClass to
    * process ThreadTasks assigned to them. This is the mechanism by which we
    * avoid deadlock due to a given loadClass request requiring multiple UCLs
    * to be involved. Any thread active in loadClass with the monitor held
    * processes class loading tasks that must be handled by its UCL3. The
    * active set of threads loading classes form a pool of cooperating threads.
    */
   public static void nextTask(Thread t, ClassLoadingTask task,
      UnifiedLoaderRepository3 repository)
      throws InterruptedException
   {
      boolean trace = log.isTraceEnabled();
      List taskList = (List) loadTasksByThread.get(t);
      synchronized( task )
      {
         // There may not be any ThreadTasks
         while( taskList.size() == 0 && task.threadTaskCount != 0 )
         {
            /* There are no more tasks for the calling thread to execute, so the
            calling thread must wait until the task.threadTaskCount reaches 0
             */
            if( trace )
               log.trace("Begin nextTask(WAIT_ON_EVENT), task="+task);
            try
            {
               task.state = ClassLoadingTask.WAIT_ON_EVENT;
               /* TODO: We are waiting with the UCL monitor held here. Is there
               a deadlock scenario that can occur due to the class we are
               waiting on needing a class from the UCL?
               */
               task.wait();
            }
            catch(InterruptedException e)
            {
               if( trace )
                  log.trace("nextTask(WAIT_ON_EVENT), interrupted, task="+task, e);
               // Abort this task attempt
               throw e;
            }
         }

         if( trace )
            log.trace("Continue nextTask("+taskList.size()+"), task="+task);

         // See if the task is complete
         if( task.threadTaskCount == 0 )
         {
            task.state = ClassLoadingTask.FINISHED;
            log.trace("End nextTask(FINISHED), task="+task);
            return;
         }
      }

      ThreadTask threadTask = (ThreadTask) taskList.remove(0);
      ClassLoadingTask loadTask = threadTask.getLoadTask();
      if( trace )
         log.trace("Begin nextTask("+taskList.size()+"), loadTask="+loadTask);

      UnifiedClassLoader3 ucl3 = (UnifiedClassLoader3) threadTask.ucl;
      try
      {
         if( threadTask.t == null )
         {
            /* This is a task that has been reassigned back to the original
            requesting thread ClassLoadingTask, so a new ThreadTask must
            be scheduled.
            */
            if( trace )
               log.trace("Rescheduling threadTask="+threadTask);
            scheduleTask(loadTask, ucl3, threadTask.order, true, trace);
         }
         else
         {
            if( trace )
               log.trace("Running threadTask="+threadTask);
            // Load the class using this thread
            threadTask.run();
         }
      }
      catch(Throwable e)
      {
         if( e instanceof ClassCircularityError && taskList.size() > 0 )
         {
            /* Reschedule this task after all existing tasks to allow the
            current load tasks which are conflicting to complete.
            */
            try
            {
               if( trace )
                  log.trace("Run failed with exception", e);
               // Reschedule and update the loadTask.threadTaskCount
               scheduleTask(loadTask, ucl3, Integer.MAX_VALUE, false, trace);
            }
            catch(ClassNotFoundException ex)
            {
               loadTask.loadException = e;
               log.warn("Failed to reschedule task after CCE", ex);
            }
            if( trace )
               log.trace("Post CCE state, loadTask="+loadTask);
         }
         else
         {
            loadTask.loadException = e;
            if( trace )
               log.trace("Run failed with exception", e);
         }
      }
      finally
      {
         // We must release the loadLock acquired in beginLoadTask
         if( threadTask.releaseInNextTask == true )
         {
            if( trace )
               log.trace("Releasing loadLock and ownership of UCL: "+threadTask.ucl);
            synchronized( registrationLock )
            {
               loadClassThreads.remove(threadTask.ucl);
            }
            synchronized( threadTask.ucl )
            {
               ucl3.release();
               ucl3.notifyAll();
            }
         }
      }

      // If the ThreadTasks are complete mark the ClassLoadingTask finished
      if( loadTask.threadTaskCount == 0 )
      {
         Class loadedClass = threadTask.getLoadedClass();
         if( loadedClass != null )
         {
            ClassLoader loader = loadedClass.getClassLoader();
            // Place the loaded class into the repositry cache
            repository.cacheLoadedClass(threadTask.getClassname(),
               loadedClass, loader);
         }
         synchronized( loadTask )
         {
            if( trace )
               log.trace("Notifying task of thread completion, loadTask:"+loadTask);
            loadTask.state = ClassLoadingTask.FINISHED;
            loadTask.notify();
         }
      }
      if( trace )
         log.trace("End nextTask("+taskList.size()+"), loadTask="+loadTask);
   }

   /** Complete a ClassLoadingTask. This is called by UCL3.loadClass to indicate
    * that the thread is existing the loadClass method.
    */
   public static void endLoadTask(ClassLoadingTask task)
   {
      boolean trace = log.isTraceEnabled();
      if( trace )
         log.trace("Begin endLoadTask, task="+task);

      // Unregister as the owning thread and notify any waiting threads
      synchronized( registrationLock )
      {
         loadClassThreads.remove(task.requestingClassLoader);
         registrationLock.notifyAll();
      }

      // Any ThreadTasks associated with this thread must be reassigned
      List taskList = (List) loadTasksByThread.get(task.requestingThread);
      int size = taskList != null ? taskList.size() : 0;
      synchronized( taskList )
      {
         for(int i = 0; i < size; i ++)
         {
            ThreadTask threadTask = (ThreadTask) taskList.remove(0);
            ClassLoadingTask loadTask = threadTask.getLoadTask();
            /* Synchronize on loadTask and reassign the thread task back to the
            requesting thread of loadTask. We need to synchronize on loadTask
            to ensure that the transfer of this task back to loadTask.requestingThread
            is atomic wrt loadTask.requestingThread checking its task list.
            */
            synchronized( loadTask )
            {
               if( trace )
                  log.trace("Reassigning task: "+threadTask+", to: "+loadTask.requestingThread);
               threadTask.t = null;
               // Insert the task into the front of requestingThread task list
               List toTaskList = (List) loadTasksByThread.get(loadTask.requestingThread);
               toTaskList.add(0, threadTask);
               loadTask.state = ClassLoadingTask.NEXT_EVENT;
               loadTask.notify();
            }
         }
      }
   }

   /** Invoked to create a ThreadTask to assign a thread to the task of
    * loading the class of ClassLoadingTask.
    *
    * @param task the orginating UCL3.loadClass task for which the thread
    * @param ucl the UCL3 the ThreadTask will call loadClassLocally on
    * @param order the heirachical ordering of the task
    * @param reschedule a boolean indicating if this task is being rescheduled
    *    with another UCL3
    * @param trace the Logger trace level flag
    * @throws ClassNotFoundException
    */
   static private void scheduleTask(ClassLoadingTask task, UnifiedClassLoader3 ucl,
      int order, boolean reschedule, boolean trace) throws ClassNotFoundException
   {
      Thread t = null;
      boolean releaseInNextTask = false;
      ThreadTask subtask = null;
      List taskList = null;
      synchronized( registrationLock )
      {
         // Find the thread that owns the ucl
         t = (Thread) loadClassThreads.get(ucl);
         if( t == null )
         {
            /* There is no thread in the UCL.loadClass yet that has registered
               as the owning thread. We must attempt to acquire the loadLock
               and if we cannot, wait until the thread entering UCL.loadClass
               gets to the registerLoaderThread call. By the time we are
               notified, the thread coule in fact have exited loadClass, so
               we either assign the task to the thread, or take ownership of
               the UCL.
             */
            while( t == null && ucl.attempt(1) == false )
            {
               if( trace )
                  log.trace("Waiting for owner of UCL: "+ucl);
               try
               {
                  registrationLock.wait();
               }
               catch(InterruptedException e)
               {
                  String msg = "Interrupted waiting for registration notify,"
                     + " classame: "+task.classname;
                  throw new ClassNotFoundException(msg);
               }

               t = (Thread) loadClassThreads.get(ucl);
               if( trace )
                  log.trace("Notified that UCL owner is: "+t);
            }

            // Get the thread registered as owning the UCL.loadClass lock
            t = (Thread) loadClassThreads.get(ucl);
            if( t == null )
            {
               // There is no such thread, register as the owner
               releaseInNextTask = true;
               t = task.requestingThread;
               Object prevThread = loadClassThreads.put(ucl, t);
               if( trace )
               {
                  log.trace("scheduleTask, taking ownership of ucl="+ucl
                     +", t="+t+", prevT="+prevThread);
               }
            }
         }

         // Now that we have the UCL owner thread, create and assign the task
         subtask = task.newThreadTask(ucl, t, order, reschedule,
            releaseInNextTask);
         // Add the task to the owning thread
         taskList = (List) loadTasksByThread.get(t);
         synchronized( taskList )
         {
            taskList.add(subtask);
            // Order the tasks by either the heirarchial order, or the repository order
            Collections.sort(taskList, task.taskComparator);
         }
      }

      if( trace )
         log.trace("scheduleTask("+taskList.size()+"), created subtask: "+subtask);
   } 
}
