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

package org.jboss.deployment.scope;

import java.net.URL;
import java.util.Set;
import java.util.Map;
import java.util.Iterator;

import org.jboss.logging.Logger;

/**
 * Scope is a manager/mediator that connects several ScopedURLClassLoaders
 * with each other and computes their dependencies. The locks used in the scope
 * implementation are quite coarse-grained, maybe thread-unfriendly, but the
 * rationale is that classloading a) happens not too often (hopefully) in the
 * lifecycle of an application b) will dispatch only in special cases (where applications depliberately
 * share classes) to this scope class and c) is optimized by caching the locations.
 * @author  cgjung
 * @version 0.8
 */

public class Scope {
    
    /** keeps a map of class loaders that participate in this scope */
    final protected Map classLoaders=new java.util.HashMap();
    
    /** keeps a hashtable of dependencies between the classLoaders */
    final protected Map dependencies=new java.util.HashMap();
    
    /** keeps a hashtable of class appearances */
    final protected Map classLocations=new java.util.HashMap();
    
    /** keeps a hashtable of resource appearances */
    final protected Map resourceLocations=new java.util.HashMap();
    
    /** keeps a reference to a logger which which to interact */
    final protected Logger log;
    
    /** Creates new Scope */
    public Scope(Logger log) {
        this.log=log;
    }
    
    /** registers a classloader in this scope */
    public ScopedURLClassLoader registerClassLoader(ScopedURLClassLoader loader) {
        // must synchronize not to collide with deregistrations and
        // dependency logging
        synchronized(classLoaders) {
            return (ScopedURLClassLoader) classLoaders.put(loader.deployment.getLocalUrl(),loader);
        }
    }
    
    
    /** deRegisters a classloader in this scope
     *  removes all cached data related to this classloader
     */
    public ScopedURLClassLoader deRegisterClassLoader(ScopedURLClassLoader loader) {
        // synchronized not to collide with registrations
        // and dependency logging
        synchronized(classLoaders) {
            // remove class locations
            clearByValue(classLocations,loader);
            // remove resource locations
            clearByValue(resourceLocations,loader);
            // remove dependency annotations
            dependencies.remove(loader);
            // and remove the loader
            return (ScopedURLClassLoader) classLoaders.remove(loader.deployment.getLocalUrl());
        }
    }
    
    /** helper method that will clear all entries from a map
     *  with a dedicated target value.
     */
    protected void clearByValue(Map map, Object value) {
        Iterator values=map.values().iterator();
        while(values.hasNext()) {
            if(values.next().equals(value))
                // uses the very useful remove method of the value iterator!
                values.remove();
        }
    }
    
    /** returns the classLoaders that a particular classLoader is
     *  dependent on. Should be called after locking classLoaders
     */
    public Set getDependentClassLoaders(ScopedURLClassLoader loader) {
        Set result=(Set) dependencies.get(loader);
        if(result==null)
            result=new java.util.HashSet();
        return result;
    }
    
    /** adds a dependency between two classloaders. this can be called
     *  from within application threads that require resource loading.
     *  Should be called after locking classLoaders
     */
    protected boolean addDependency(ScopedURLClassLoader source, ScopedURLClassLoader target) {
        // no rescursions necessary (but not volatile for the code)
        if(source !=null && target!=null && !source.equals(target)) {
            
            Set deps=(Set) dependencies.get(target);
            
            if(deps==null) {
                deps=new java.util.HashSet();
                dependencies.put(target,deps);
            }
            
            log.debug("Adding dependency from deployment "+source+":"+source.deployment.getLocalUrl()+" to deployment "+
                target+":"+target.deployment.getLocalUrl());
            
            return deps.add(source);
        } else
            return false;
    }
    
    /** loads a class on behalf of a given classloader */
    public Class loadClass(String className, boolean resolve, ScopedURLClassLoader source)
    throws ClassNotFoundException {
        
        // short look into the class location cache, is synchronized in
        // case that the relevant target is simultaneously teared down
        synchronized(classLoaders) {
            ScopedURLClassLoader target= (ScopedURLClassLoader)
            classLocations.get(className);
            
            // its there, so log and load it
            if(target!=null) {
                addDependency(source,target);
                // we can be sure that the target loader
                // has the class already in its own cache
                // so this call should not cost much
                return target.loadClass(className,resolve);
            }
            
            // otherwise we do a big lookup
            Iterator allLoaders=classLoaders.values().iterator();
        
            while(allLoaders.hasNext()) {
                target=(ScopedURLClassLoader) allLoaders.next();
            
            // no recursion, please
            if(!target.equals(source)) {
                try{
                    Class foundClass=target.loadClassProperly(className,resolve);
                    classLocations.put(className,target);
                    addDependency(source,target);
                    return foundClass;
                } catch(ClassNotFoundException e) {
                    // proceed with the next loaders in scope
                }
            }
        } // while
        
        // no loader in the scope has been able to load the class
        throw new ClassNotFoundException("could not resolve class "+
        className+" in scope.");
        
        } // sync
    }
    
    /** gets a URL on behalf of a given classloader which may be null
     *  in case that we do not check dependencies */
    public URL getResource(String name, ScopedURLClassLoader source) {
        
        // short look into the resource location cache, is synchronized in
        // case that the relevant target is simultaneously teared down
        synchronized(classLoaders) {
            ScopedURLClassLoader target= (ScopedURLClassLoader)
                resourceLocations.get(name);
            
            // its there, so log and load it
            if(target!=null)
                addDependency(source,target);
        
           // the lock is released here, so that other threads could run too
            if(target!=null)
                return target.getResource(name);
        
            // otherwise we do a big lookup
            Iterator allLoaders=classLoaders.values().iterator();
        
            while(allLoaders.hasNext()) {
                target=(ScopedURLClassLoader) allLoaders.next();
            
            // no recursion, please
            if(!target.equals(source)) {
                    URL foundResource=target.getResourceProperly(name);
                    if(foundResource!=null) {
                        resourceLocations.put(name,target);
                        addDependency(source,target);
                        return foundResource;
                    }
            }
        } // while
        
        // no loader in the scope has been able to load the resource
        return null;
        
        } // sync
    }

}
