diff --git a/util/classpath/src/main/scala/sbt/ModuleUtilities.scala b/util/classpath/src/main/scala/sbt/ModuleUtilities.scala index ab017c1b6..d939c040b 100644 --- a/util/classpath/src/main/scala/sbt/ModuleUtilities.scala +++ b/util/classpath/src/main/scala/sbt/ModuleUtilities.scala @@ -5,7 +5,10 @@ package sbt object ModuleUtilities { - def getObject(className: String, loader: ClassLoader) = + /** Reflectively loads and returns the companion object for top-level class `className` from `loader`. + * The class name should not include the `$` that scalac appends to the underlying jvm class for + * a companion object. */ + def getObject(className: String, loader: ClassLoader): AnyRef = { val obj = Class.forName(className + "$", true, loader) val singletonField = obj.getField("MODULE$") diff --git a/util/classpath/src/main/scala/sbt/ReflectUtilities.scala b/util/classpath/src/main/scala/sbt/ReflectUtilities.scala index c787bb9f5..4cef2e041 100644 --- a/util/classpath/src/main/scala/sbt/ReflectUtilities.scala +++ b/util/classpath/src/main/scala/sbt/ReflectUtilities.scala @@ -7,7 +7,8 @@ import scala.collection._ object ReflectUtilities { - def transformCamelCase(name: String, separator: Char) = + /** Converts the camelCase String `name` to lowercase separated by `separator`. */ + def transformCamelCase(name: String, separator: Char): String = { val buffer = new StringBuilder for(char <- name) @@ -33,6 +34,9 @@ object ReflectUtilities flatMap(_.getDeclaredFields). map(f => (f.getName, f)):_*) + /** Collects all `val`s of type `T` defined on value `self`. + * The returned Map maps the name of each `val` to its value. + * This depends on scalac implementation details to determine what is a `val` using only Java reflection. */ def allValsC[T](self: AnyRef, clazz: Class[T]): immutable.SortedMap[String, T] = { var mappings = new immutable.TreeMap[String, T] @@ -51,7 +55,14 @@ object ReflectUtilities } mappings } + + /** Collects all `val`s of type `T` defined on value `self`. + * The returned Map maps the name of each `val` to its value. + * This requires an available `Manifest` for `T` and depends on scalac implementation details to determine + * what is a `val` using only Java reflection. */ def allVals[T](self: AnyRef)(implicit mt: scala.reflect.Manifest[T]): immutable.SortedMap[String, T] = allValsC(self, mt.runtimeClass).asInstanceOf[immutable.SortedMap[String,T]] } + +/** An exception to indicate that while traversing the `val`s for an instance of `className`, the `val` named `valName` was `null`. */ final class UninitializedVal(val valName: String, val className: String) extends RuntimeException("val " + valName + " in class " + className + " was null.\nThis is probably an initialization problem and a 'lazy val' should be used.") \ No newline at end of file diff --git a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala index 7eb9e1e11..c01584e96 100644 --- a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala +++ b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala @@ -105,6 +105,8 @@ final class FilteredLoader(parent: ClassLoader, filter: ClassFilter) extends Cla throw new ClassNotFoundException(className) } } + +/** Defines a filter on class names. */ trait ClassFilter { def include(className: String): Boolean @@ -114,18 +116,31 @@ abstract class PackageFilter(packages: Iterable[String]) extends ClassFilter require(packages.forall(_.endsWith("."))) protected final def matches(className: String): Boolean = packages.exists(className.startsWith) } +/** Excludes class names that begin with one of the packages in `exclude`. +* Each package name in `packages` must end with a `.` */ final class ExcludePackagesFilter(exclude: Iterable[String]) extends PackageFilter(exclude) { def include(className: String): Boolean = !matches(className) } + +/** Includes class names that begin with one of the packages in `include`. +* Each package name in `include` must end with a `.` */ final class IncludePackagesFilter(include: Iterable[String]) extends PackageFilter(include) { def include(className: String): Boolean = matches(className) } +/** Configures a [[NativeCopyLoader]]. +* The loader will provide native libraries listed in `explicitLibraries` and on `searchPaths` by copying them to `tempDirectory`. +* If `tempDirectory` is unique to the class loader, this ensures that the class loader gets a unique path for +* the native library and avoids the restriction on a native library being loaded by a single class loader. */ final class NativeCopyConfig(val tempDirectory: File, val explicitLibraries: Seq[File], val searchPaths: Seq[File]) + +/** Loads native libraries from a temporary location in order to work around the jvm native library uniqueness restriction. +* See [[NativeCopyConfig]] for configuration details. */ trait NativeCopyLoader extends ClassLoader { + /** Configures this loader. See [[NativeCopyConfig]] for details. */ protected val config: NativeCopyConfig import config._ diff --git a/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala b/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala index 9d7ea972b..8d1ded498 100644 --- a/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala +++ b/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala @@ -7,6 +7,7 @@ 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") @@ -14,7 +15,20 @@ final class NullLoader extends ClassLoader 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) { @@ -76,6 +90,7 @@ class DualLoader(parentA: ClassLoader, aOnlyClasses: String => Boolean, aOnlyRes } } +/** 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 diff --git a/util/classpath/src/main/scala/sbt/classpath/RawURL.scala b/util/classpath/src/main/scala/sbt/classpath/RawURL.scala index 645dcb10d..1f25954a2 100644 --- a/util/classpath/src/main/scala/sbt/classpath/RawURL.scala +++ b/util/classpath/src/main/scala/sbt/classpath/RawURL.scala @@ -10,10 +10,17 @@ package classpath object RawURL { + /** Constructs a URL with scheme `raw` and path `file` that will return the bytes for `value` in the platform default encoding + * when a connection to the URL is opened. */ def apply(file: String, value: String): URL = apply(file, value.getBytes) + + /** Constructs a URL with scheme `raw` and path `file` that will return the bytes `value` when a connection to the URL is opened. */ def apply(file: String, value: Array[Byte]): URL = apply(file)(new ByteArrayInputStream(value)) + + /** Constructs a URL with scheme `raw` and path `file` that will use `value` to construct the `InputStream` used when a connection + * to the URL is opened. */ def apply(file: String)(value: => InputStream): URL = new URL("raw", null, -1, file, new RawStreamHandler(value)) @@ -31,14 +38,20 @@ object RawURL } } +/** A ClassLoader that looks up resource requests in a `Map` prior to the base ClassLoader's resource lookups. */ trait RawResources extends FixedResources { + /** The map from resource paths to the raw String content to provide via the URL returned by [[findResource]] or [[findResources]]. */ protected def resources: Map[String, String] override protected final val resourceURL = resources.transform(RawURL.apply) } + +/** A ClassLoader that looks up resource requests in a `Map` prior to the base ClassLoader's resource lookups. */ trait FixedResources extends ClassLoader { + /** The map from resource paths to URL to provide in [[findResource]] and [[findResources]]. */ protected def resourceURL: Map[String, URL] + override def findResource(s: String): URL = resourceURL.getOrElse(s, super.findResource(s)) import java.util.Collections.{enumeration, singletonList}