/* JFox, the OpenSource J2EE Application Server
 *
 * Distributable under GNU LGPL license by gun.org
 * more details please visit http://www.huihoo.org/jfox
 */
package org.jfox.framework.loader;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

/**
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class LocalClassLoader extends URLClassLoader {
    /**
     * global Class ClassLoaderRepository
     */
    private ClassLoaderRepository repo;

    /**
     * load only private libs
     */
    private NestedURLClassLoader privateLoader;

    /**
     * the jars in the urls can be load by Repository
     *
     * @param urls
     */
    public LocalClassLoader(URL[] urls, ClassLoaderRepository repo) {
        super(urls, null);
        privateLoader = new NestedURLClassLoader(new URL[0]);
        if(repo != null) {
            repo.addClassLoader(this);
        }
        this.repo = repo;
    }

    /**
     * only find in the component urls
     * will cache it in repo
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    public Class findClassPublic(String name) throws ClassNotFoundException {
        Class clz = super.findClass(name);
        repo.cacheClass(name, clz);
        return clz;
    }

    public synchronized Class loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    /**
     * must implements this Method, becase child class loader will use it
     *
     * @param name
     * @param resolve
     * @return
     * @throws ClassNotFoundException
     */
    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class c = findLoadedClass(name);
        if(c == null) {
            try {
                // first load class from repo common jars
                c = repo.loadClassSuper(name);
            }
            catch(ClassNotFoundException e) {
                c = findClass(name);
            }
        }
        if(resolve) {
            resolveClass(c);
        }
        return c;
    }

    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            // load from repo caches, the cached class may be loaded by other LocalClassLoader
            // but must guarentee it must be load once(because of the reference each other), so must reuse it
            return repo.findClassCached(name);
        }
        catch(ClassNotFoundException e1) {
            try {
                // load in this component's pub urls
                return findClassPublic(name);
            }
            catch(ClassNotFoundException e2) {
                try {
                    // load by other component's pub urls in repo
                    return repo.findClassWithout(name, this);
                }
                catch(ClassNotFoundException e3) {

                    try {
                        // at last try to load the by private libs of this
                        return findClassPrivate(name);
                    }
                    catch(ClassNotFoundException e4) {
                    }
                }
            }
        }
        throw new ClassNotFoundException(name);
    }

    private Class findClassPrivate(String name) throws ClassNotFoundException {
        return privateLoader.findClass(name);
    }

    /**
     * add usr can only load by the private Class
     *
     * @param url
     */
    public void addURL(URL url) {
        privateLoader.addURL(url);
    }

    public URL getResource(String name) {
        URL url = repo.getResource(name);

        if(url == null) {
            url = super.getResource(name);
        }

        if(url == null) {
            url = privateLoader.getResource(name);
        }

        if(url == null) {
            url = findResource(name);
        }
        return url;
    }


    /**
     * will invoke repo.findResource
     *
     * @param name
     * @return
     */
    public URL findResource(String name) {
        URL url = super.findResource(name);
        if(url == null) {
            url = privateLoader.findResource(name);
        }

        if(url == null) {
            url = repo.findResource(name);
        }
        return url;
    }

    /**
     * will invoke repo.getResources
     *
     * @param name
     * @return
     * @throws IOException
     */
    public Enumeration findResources(String name) throws IOException {
        List list = new ArrayList();
        list.addAll(Collections.list(super.findResources(name)));
        list.addAll(Collections.list(privateLoader.findResources(name)));

        /*
         * must use Repository to getResources, so child ClassLoader can getResource from repo
         * example, web application can use ClassLoader to find jndi.properties
         */
        list.addAll(Collections.list(repo.getResources(name)));
        return Collections.enumeration(list);
    }

    /**
     * exporting private urls is need by jsp compiler
     * jsp compiler use getURLs to find the classpath
     *
     * @return
     */
    public URL[] getURLs() {
        List urls = new ArrayList();
        urls.addAll(Arrays.asList(super.getURLs()));
        urls.addAll(Arrays.asList(privateLoader.getURLs()));
        return (URL[]) urls.toArray(new URL[0]);
    }

    public String toString() {
        return "LocalClassLoader" + Arrays.asList(getURLs());
    }
}

