/*
 * Copyright 2010 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.gradle.internal.classloader;

import org.gradle.internal.reflect.JavaReflectionUtil;
import org.gradle.internal.reflect.JavaMethod;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * A {@code ClassLoader} which delegates to multiple parent ClassLoaders.
 *
 * Note: It's usually a good idea to add a {@link CachingClassLoader} between this ClassLoader and any
 * ClassLoaders that use it as a parent, to prevent every path in the ClassLoader graph being searched.
 */
public class MultiParentClassLoader extends ClassLoader implements ClassLoaderHierarchy {
    private final List<ClassLoader> parents;
    private final JavaMethod<ClassLoader, Package[]> getPackagesMethod;
    private final JavaMethod<ClassLoader, Package> getPackageMethod;

    public MultiParentClassLoader(ClassLoader... parents) {
        this(Arrays.asList(parents));
    }

    public MultiParentClassLoader(Collection<? extends ClassLoader> parents) {
        super(null);
        this.parents = new CopyOnWriteArrayList<ClassLoader>(parents);
        getPackagesMethod = JavaReflectionUtil.method(ClassLoader.class, Package[].class, "getPackages");
        getPackageMethod = JavaReflectionUtil.method(ClassLoader.class, Package.class, "getPackage", String.class);
    }

    public void addParent(ClassLoader parent) {
        parents.add(parent);
    }

    public void visit(ClassLoaderVisitor visitor) {
        visitor.visitSpec(new Spec());
        for (ClassLoader parent : parents) {
            visitor.visitParent(parent);
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        for (ClassLoader parent : parents) {
            try {
                return parent.loadClass(name);
            } catch (ClassNotFoundException e) {
                // Expected
            }
        }
        throw new ClassNotFoundException(String.format("%s not found.", name));
    }

    @Override
    protected Package getPackage(String name) {
        for (ClassLoader parent : parents) {
            Package p = getPackageMethod.invoke(parent, name);
            if (p != null) {
                return p;
            }
        }
        return null;
    }

    @Override
    protected Package[] getPackages() {
        Set<Package> packages = new LinkedHashSet<Package>();
        for (ClassLoader parent : parents) {
            Package[] parentPackages = getPackagesMethod.invoke(parent);
            packages.addAll(Arrays.asList(parentPackages));
        }
        return packages.toArray(new Package[packages.size()]);
    }

    @Override
    public URL getResource(String name) {
        for (ClassLoader parent : parents) {
            URL resource = parent.getResource(name);
            if (resource != null) {
                return resource;
            }
        }
        return null;
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        Set<URL> resources = new LinkedHashSet<URL>();
        for (ClassLoader parent : parents) {
            Enumeration<URL> parentResources = parent.getResources(name);
            while (parentResources.hasMoreElements()) {
                resources.add(parentResources.nextElement());
            }
        }
        return Collections.enumeration(resources);
    }

    public static class Spec extends ClassLoaderSpec {
        @Override
        public boolean equals(Object obj) {
            return obj != null && obj.getClass().equals(Spec.class);
        }

        @Override
        public int hashCode() {
            return getClass().getName().hashCode();
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof MultiParentClassLoader)) {
            return false;
        }

        MultiParentClassLoader that = (MultiParentClassLoader) o;

        if (!parents.equals(that.parents)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return parents.hashCode();
    }
}

@@ -690,30 +690,22 @@
 ect.Java
-ReflectionUtil
+Method
 ;%0Aimport
@@ -737,22 +737,30 @@
 ect.Java
-Method
+ReflectionUtil
 ;%0A%0Aimpor
@@ -1252,24 +1252,25 @@
 Hierarchy %7B%0A
+%0A
     private 
@@ -1273,70 +1273,104 @@
 ate 
-final List%3CClassLoader%3E parents;%0A    private final JavaM
+static final JavaMethod%3CClassLoader, Package%5B%5D%3E GET_PACKAGES_METHOD = JavaReflectionUtil.m
 ethod
-%3C
+(
 Clas
@@ -1368,32 +1368,38 @@
 thod(ClassLoader
+.class
 , Package%5B%5D%3E get
@@ -1393,18 +1393,25 @@
 ackage%5B%5D
-%3E 
+.class, %22
 getPacka
@@ -1413,22 +1413,18 @@
 Packages
-Method
+%22)
 ;%0A    pr
@@ -1429,16 +1429,23 @@
 private 
+static 
 final Ja
@@ -1475,32 +1475,170 @@
 ackage%3E 
-getPackageMethod
+GET_PACKAGE_METHOD = JavaReflectionUtil.method(ClassLoader.class, Package.class, %22getPackage%22, String.class);%0A%0A    private final List%3CClassLoader%3E parents
 ;%0A%0A    p
@@ -1912,230 +1912,8 @@
 s);%0A
-        getPackagesMethod = JavaReflectionUtil.method(ClassLoader.class, Package%5B%5D.class, %22getPackages%22);%0A        getPackageMethod = JavaReflectionUtil.method(ClassLoader.class, Package.class, %22getPackage%22, String.class);%0A
     
@@ -2735,24 +2735,26 @@
 p = 
-getPackageMethod
+GET_PACKAGE_METHOD
 .inv
@@ -3083,25 +3083,27 @@
 s = 
-getPackagesMethod
+GET_PACKAGES_METHOD
 .inv
@@ -4587,21 +4587,23 @@
         
-if (!
+return 
 parents.
@@ -4626,68 +4626,8 @@
 nts)
-) %7B%0A            return false;%0A        %7D%0A%0A        return true
 ;%0A  

Make method objects constants.