Skip to content

Commit

Permalink
Fixes classLocation and classfileLocation
Browse files Browse the repository at this point in the history
This fixes classLocation family of methods and classfileLocation that were broken in sbt#181.
  • Loading branch information
eed3si9n committed Oct 4, 2018
1 parent 9d67949 commit 7d36919
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 4 deletions.
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ val io = (project in file("io"))
Vector(scalaCompiler.value % Test, scalaCheck % Test, scalatest.value % Test)
} ++ Vector(swovalFiles),
libraryDependencies ++= Seq(jna, jnaPlatform),

Test / fork := true,

sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
initialCommands in console += "\nimport sbt.io._, syntax._",
mimaPreviousArtifacts := (CrossVersion partialVersion scalaVersion.value match {
Expand Down
61 changes: 57 additions & 4 deletions io/src/main/scala/sbt/io/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object IO {
* Note that Java standard library classes typically do not have a location associated with them.
*/
def classLocationPath(cl: Class[_]): NioPath = {
val u = classfileLocation(cl)
val u = classLocation(cl)
val p = u.getProtocol match {
case FileScheme => Option(toFile(u).toPath)
case "jar" => urlAsFile(u) map { _.toPath }
Expand All @@ -85,7 +85,7 @@ object IO {
* Note that Java standard library classes typically do not have a location associated with them.
*/
def classLocationFileOption(cl: Class[_]): Option[File] = {
val u = classfileLocation(cl)
val u = classLocation(cl)
urlAsFile(u)
}

Expand Down Expand Up @@ -118,6 +118,59 @@ object IO {
"1.3.0")
def classLocationFile[T](implicit mf: SManifest[T]): File = classLocationFile(mf.runtimeClass)

/**
* Returns the URL to the directory or the JAR file containing the class file `cl`.
* If the location cannot be determined or it is not a file, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.
*/
def classLocation(cl: Class[_]): URL = {
def localcl: Option[URL] =
Option(cl.getProtectionDomain.getCodeSource) flatMap { codeSource =>
Option(codeSource.getLocation)
}
// This assumes that classes without code sources are System classes, and thus located in jars.
// It returns a URL that looks like jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/Integer.class
val clsfile = s"${cl.getName.replace('.', '/')}.class"
def syscl: Option[URL] =
Option(ClassLoader.getSystemClassLoader) flatMap { classLoader =>
Option(classLoader.getResource(clsfile))
}
try {
localcl
.orElse(syscl)
.map(url =>
url.getProtocol match {
case "jar" =>
val path = url.getPath
val end = path.indexOf('!')
new URL(
if (end == -1) path
else path.substring(0, end))
case "jrt" =>
val path = url.getPath
val end = path.indexOf('/', 1)
new URL("jrt",
null,
if (end == -1) path
else path.substring(0, end))
case _ => url
})
.getOrElse(sys.error("No class location for " + cl))
} catch {
case NonFatal(e) =>
e.printStackTrace()
throw e
}
}

/**
* Returns the URL to the directory or the JAR file containing the class file `cl`.
* If the location cannot be determined or it is not a file, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.
*/
def classLocation[A](implicit mf: SManifest[A]): URL =
classLocation(mf.runtimeClass)

/**
* Returns a URL for the classfile containing the given class file for type `T` (as determined by an implicit Manifest).
* If the location cannot be determined, an error is generated.
Expand All @@ -131,8 +184,8 @@ object IO {
def classfileLocation(cl: Class[_]): URL = {
val clsfile = s"${cl.getName.replace('.', '/')}.class"
def localcl: Option[URL] =
Option(cl.getProtectionDomain.getCodeSource) flatMap { codeSource =>
Option(codeSource.getLocation)
Option(cl.getClassLoader) flatMap { classLoader =>
Option(classLoader.getResource(clsfile))
}
def syscl: Option[URL] =
Option(ClassLoader.getSystemClassLoader) flatMap { classLoader =>
Expand Down
95 changes: 95 additions & 0 deletions io/src/test/scala/sbt/io/IOSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,101 @@ class IOSpec extends FunSuite {
assert(IO.getModifiedTimeOrZero(file("/not/existing/path")) == 0L)
}

// classLocation -----------

test("classLocation[Integer] should return a URL pointing to the rt.jar or java.base") {
val u = IO.classLocation[java.lang.Integer]
assert(
(u.toString == "jrt:/java.base") ||
(u.toString.startsWith("file:") && u.toString.endsWith("rt.jar") && IO.asFile(u).isFile))
}

test(
"classLocation[AbstractMap.SimpleEntry] should return a URL pointing to the rt.jar or java.base") {
val u = IO.classLocation[java.util.AbstractMap.SimpleEntry[String, String]]
assert(
(u.toString == "jrt:/java.base") ||
(u.toString.startsWith("file:") && u.toString.endsWith("rt.jar") && IO.asFile(u).isFile))
}

test("classLocation[this.type] should return a URL pointing to a directory") {
val u = IO.classLocation[this.type]
assert(IO.asFile(u).isDirectory)
}

// classLocationPath -----------

test("classLocationPath[Integer] should return NIO path pointing to the rt.jar or java.base") {
val p = IO.classLocationPath[java.lang.Integer]
assert(
((p.toString == "/modules/java.base") && (p.getFileSystem.toString == "jrt:/"))
|| (p.toString.endsWith("rt.jar") && p.toFile.isFile)
)
}

test(
"classLocationPath[AbstractMap.SimpleEntry] should return NIO path pointing to the rt.jar or java.base") {
val p = IO.classLocationPath[java.util.AbstractMap.SimpleEntry[String, String]]
assert(
((p.toString == "/modules/java.base") && (p.getFileSystem.toString == "jrt:/"))
|| (p.toString.endsWith("rt.jar") && p.toFile.isFile)
)
}

test("classLocationPath[this.type] should return NIO path pointing to a directory") {
val p = IO.classLocationPath[this.type]
assert(p.toFile.isDirectory)
}

// classLocationFileOption -----------

test("classLocationFileOption[Integer] should return File pointing to the rt.jar or None") {
val opt = IO.classLocationFileOption[java.lang.Integer]
assert(
opt match {
case Some(x) => x.getName == "rt.jar"
case None => true
}
)
}

test(
"classLocationFileOption[AbstractMap.SimpleEntry] should return File pointing to the rt.jar or None") {
val opt = IO.classLocationFileOption[java.util.AbstractMap.SimpleEntry[String, String]]
assert(
opt match {
case Some(x) => x.getName == "rt.jar"
case None => true
}
)
}

test("classLocationFileOption[this.type] should return File pointing to a directory") {
val opt = IO.classLocationFileOption[this.type]
assert(opt.get.isDirectory)
}

// classfileLocation -----------

test("classfileLocation[Integer] should return a URL pointing to *.class") {
val s = IO.classfileLocation[java.lang.Integer].toString
assert(
(s == "jrt:/java.base/java/lang/Integer.class") ||
(s.startsWith("jar:file:") && s.endsWith("!/java/lang/Integer.class")))
}

test("classfileLocation[AbstractMap.SimpleEntry] should return a URL pointing to *.class") {
val s = IO.classfileLocation[java.util.AbstractMap.SimpleEntry[String, String]].toString
assert(
(s == "jrt:/java.base/java/util/AbstractMap$SimpleEntry.class") ||
(s.startsWith("jar:file:") && s.endsWith("!/java/util/AbstractMap$SimpleEntry.class")))
}

test("classfileLocation[this.type] should return a URL pointing to *.class") {
val s = IO.classfileLocation[this.type].toString
assert(s.startsWith("file:") && s.endsWith("/sbt/io/IOSpec.class"))
}

def normalizeForWindows(s: String): String = {
s.replaceAllLiterally("""\""", "/")
}
Expand Down

0 comments on commit 7d36919

Please sign in to comment.