/*
 * Copyright 2003-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.codehaus.groovy.tools;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.HashMap;

/**
 * This ClassLoader should be used as root of class loaders. Any
 * RootLoader does have it's own classpath. When searching for a
 * class or resource this classpath will be used. Parent
 * Classloaders are ignored first. If a class or resource
 * can't be found in the classpath of the RootLoader, then parent is
 * checked.
 * <p/>
 * <b>Note:</b> this is very against the normal behavior of
 * classloaders. Normal is to first check parent and then look in
 * the resources you gave this classloader.
 * <p/>
 * It's possible to add urls to the classpath at runtime through
 * {@link <a href="#addURL(URL)">addURL(URL)</a>}
 *      <p/>
 *      <b>Why using RootLoader?</b>
 *      If you have to load classes with multiple classloaders and a
 *      classloader does know a class which depends on a class only
 *      a child of this loader does know, then you won't be able to
 *      load the class. To load the class the child is not allowed
 *      to redirect it's search for the class to the parent first.
 *      That way the child can load the class. If the child does not
 *      have all classes to do this, this fails of course.
 *      <p/>
 *      For example:
 *      <p/>
 *      <pre>
 *       parentLoader   (has classpath: a.jar;c.jar)
 *           |
 *           |
 *       childLoader    (has classpath: a.jar;b.jar;c.jar)
 *       </pre>
 *      <p/>
 *      class C (from c.jar) extends B (from b.jar)
 *      <p/>
 *      childLoader.find("C")
 *      --> parentLoader does know C.class, try to load it
 *      --> to load C.class it has to load B.class
 *      --> parentLoader is unable to find B.class in a.jar or c.jar
 *      --> NoClassDefFoundException!
 *      <p/>
 *      if childLoader had tried to load the class by itself, there
 *      would be no problem. Changing childLoader to be a RootLoader
 *      instance will solve that problem.
 *
 * @author Jochen Theodorou
 */
public class RootLoader extends URLClassLoader {

    private Map<String, Class> customClasses = new HashMap<String, Class>();

    /**
     * constructs a new RootLoader without classpath
     *
     * @param parent the parent Loader
     */
    private RootLoader(ClassLoader parent) {
        this(new URL[0], parent);
    }

    /**
     * constructs a new RootLoader with a parent loader and an
     * array of URLs as classpath
     */
    public RootLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
        // major hack here...!
        try{
            customClasses.put("org.w3c.dom.Node",super.loadClass("org.w3c.dom.Node",false));
        } catch (Exception e) { /* ignore */ }
    }

    private static ClassLoader chooseParent() {
        ClassLoader cl = RootLoader.class.getClassLoader();
        if (cl != null) return cl;
        return ClassLoader.getSystemClassLoader();
    }

    /**
     * constructs a new RootLoader with a {@link LoaderConfiguration}
     * object which holds the classpath
     */
    public RootLoader(LoaderConfiguration lc) {
        this(chooseParent());
        Thread.currentThread().setContextClassLoader(this);
        URL[] urls = lc.getClassPathUrls();
        for (URL url : urls) {
            addURL(url);
        }
    }

    /**
     * loads a class using the name of the class
     */
    protected Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
        Class c = this.findLoadedClass(name);
        if (c != null) return c;
        c = (Class) customClasses.get(name);
        if (c != null) return c;
        
        try {
            c = oldFindClass(name);
        } catch (ClassNotFoundException cnfe) {
            // IGNORE
        }
        if (c == null) c = super.loadClass(name, resolve);

        if (resolve) resolveClass(c);

        return c;
    }

    /**
     * returns the URL of a resource, or null if it is not found
     */
    public URL getResource(String name) {
        URL url = findResource(name);
        if (url == null) url = super.getResource(name);
        return url;
    }

    /**
     * adds an url to the classpath of this classloader
     */
    public void addURL(URL url) {
        super.addURL(url);
    }

    private Class oldFindClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }

    protected Class findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

@@ -4045,24 +4045,37 @@
   protected 
+synchronized 
 Class loadCl

add sychronized flag to loadClass to ensure since internal structures are not thread safe