/* sbt -- Simple Build Tool
 * Copyright 2008, 2009 Mark Harrah
 */
package sbt
package classpath

import java.net.URL
import java.util.Enumeration

/** A class loader that always fails to load classes and resources. */
final class NullLoader extends ClassLoader
{
	override final def loadClass(className: String, resolve: Boolean): Class[_] = throw new ClassNotFoundException("No classes can be loaded from the null loader")
	override def getResource(name: String): URL = null
	override def getResources(name: String): Enumeration[URL] = null
}

/** Exception thrown when `loaderA` and `loaderB` load a different Class for the same name. */
class DifferentLoaders(message: String, val loaderA: ClassLoader, val loaderB: ClassLoader) extends ClassNotFoundException(message)

/** A ClassLoader with two parents `parentA` and `parentB`.  The predicates direct lookups towards one parent or the other.
*
* If `aOnlyClasses` returns `true` for a class name, class lookup delegates to `parentA` only.
* Otherwise, if `bOnlyClasses` returns `true` for a class name, class lookup delegates to `parentB` only.
* If both `aOnlyClasses` and `bOnlyClasses` are `false` for a given class name, both class loaders must load the same Class or
* a [[DifferentLoaders]] exception is thrown.
*
* If `aOnlyResources` is `true` for a resource path, lookup delegates to `parentA` only.
* Otherwise, if `bOnlyResources` is `true` for a resource path, lookup delegates to `parentB` only.
* If neither are `true` for a resource path and either `parentA` or `parentB` return a valid URL, that valid URL is returned.
**/
class DualLoader(parentA: ClassLoader, aOnlyClasses: String => Boolean, aOnlyResources: String => Boolean,
	parentB: ClassLoader, bOnlyClasses: String => Boolean, bOnlyResources: String => Boolean) extends ClassLoader(new NullLoader)
{
	def this(parentA: ClassLoader, aOnly: String => Boolean, parentB: ClassLoader, bOnly: String => Boolean) =
		this(parentA, aOnly, aOnly, parentB, bOnly, bOnly)
	override final def loadClass(className: String, resolve: Boolean): Class[_] =
	{
		val c =
			if(aOnlyClasses(className))
				parentA.loadClass(className)
			else if(bOnlyClasses(className))
				parentB.loadClass(className)
			else
			{
				val classA = parentA.loadClass(className)
				val classB = parentB.loadClass(className)
				if(classA.getClassLoader eq classB.getClassLoader)
					classA
				else
					throw new DifferentLoaders("Parent class loaders returned different classes for '" + className + "'", classA.getClassLoader, classB.getClassLoader)
			}
		if(resolve)
			resolveClass(c)
		c
	}
	override def getResource(name: String): URL =
	{
		if(aOnlyResources(name))
			parentA.getResource(name)
		else if(bOnlyResources(name))
			parentB.getResource(name)
		else
		{
			val urlA = parentA.getResource(name)
			val urlB = parentB.getResource(name)
			if(urlA eq null)
				urlB
			else
				urlA
		}
	}
	override def getResources(name: String): Enumeration[URL] =
	{
		if(aOnlyResources(name))
			parentA.getResources(name)
		else if(bOnlyResources(name))
			parentB.getResources(name)
		else
		{
			val urlsA = parentA.getResources(name)
			val urlsB = parentB.getResources(name)
			if(urlsA eq null)
				urlsB
			else if(urlsB eq null)
				urlsA
			else
				new DualEnumeration(urlsA, urlsB)
		}
	}
}

/** Concatenates `a` and `b` into a single `Enumeration`.*/
final class DualEnumeration[T](a: Enumeration[T], b: Enumeration[T]) extends Enumeration[T]
{
	// invariant: current.hasMoreElements or current eq b
	private[this] var current = if(a.hasMoreElements) a else b
	def hasMoreElements = current.hasMoreElements
	def nextElement =
	{
		val element = current.nextElement
		if(!current.hasMoreElements)
			current = b
		element
	}
}