Migrates Treeview.scala to use Contraband

Migrates TreeView.scala to use Contraband from scala.util.parsing.json,
because this is now deprecated.
The TreeView logic is used in the dependencyBrowseTree task.
This commit is contained in:
Amina Adewusi 2021-10-22 20:09:48 +01:00
parent bcbef795e7
commit 3c81e08fa2
7 changed files with 129 additions and 50 deletions

View File

@ -10,24 +10,20 @@ package internal
package graph
package rendering
import sbt.internal.graph.codec.JsonProtocol.ModuleModelFormat
import sbt.io.IO
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.io.{ File, FileOutputStream, InputStream, OutputStream }
import java.net.URI
import scala.annotation.nowarn
import scala.annotation.tailrec
import scala.util.parsing.json.JSONArray
import scala.util.parsing.json.JSONObject
import scala.annotation.{ nowarn, tailrec }
@nowarn object TreeView {
def createJson(graph: ModuleGraph): String = {
val trees = graph.roots
val moduleModels = graph.roots
.map(module => processSubtree(graph, module))
.toList
JSONArray(trees).toString
val js = moduleModels.map(Converter.toJsonUnsafe(_))
js.map(CompactPrinter).mkString("[", ",", "]")
}
def createLink(graphJson: String, targetDirectory: File): URI = {
@ -43,24 +39,26 @@ import scala.util.parsing.json.JSONObject
graph: ModuleGraph,
module: Module,
parents: Set[GraphModuleId] = Set()
): JSONObject = {
): ModuleModel = {
val cycle = parents.contains(module.id)
val dependencies = if (cycle) List() else graph.dependencyMap.getOrElse(module.id, List())
val children =
dependencies.map(dependency => processSubtree(graph, dependency, parents + module.id)).toList
moduleAsJson(module, cycle, children)
dependencies
.map(dependency => processSubtree(graph, dependency, parents + module.id))
.toVector
moduleAsModuleAgain(module, cycle, children)
}
private def moduleAsJson(
private def moduleAsModuleAgain(
module: Module,
isCycle: Boolean,
children: List[JSONObject]
): JSONObject = {
children: Vector[ModuleModel]
): ModuleModel = {
val eviction = module.evictedByVersion.map(version => s" (evicted by $version)").getOrElse("")
val cycle = if (isCycle) " (cycle)" else ""
val error = module.error.map(err => s" (errors: $err)").getOrElse("")
val text = module.id.idString + eviction + error + cycle
JSONObject(Map("text" -> text, "children" -> JSONArray(children)))
ModuleModel(text, children)
}
def saveResource(resourcePath: String, to: File): Unit = {

View File

@ -5,51 +5,48 @@
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
package graph
package rendering
package sbt.internal.graph.rendering
import org.scalatest.DiagrammedAssertions
import org.scalatest.FunSuite
import scala.annotation.nowarn
import scala.util.parsing.json.JSONArray
import scala.util.parsing.json.JSONObject
@nowarn("msg=class JSONObject in package json is deprecated")
class TreeViewTest extends FunSuite with DiagrammedAssertions {
import org.scalatest.{ FlatSpec, Matchers }
import sbt.internal.graph.rendering.TreeView.createJson
import sbt.internal.graph.{ GraphModuleId, Module, ModuleGraph, ModuleModel }
class TreeViewTest extends FlatSpec with Matchers {
val modA = GraphModuleId("orgA", "nameA", "1.0")
val modB = GraphModuleId("orgB", "nameB", "2.0")
val modC = GraphModuleId("orgC", "nameC", "3.0")
val graph = ModuleGraph(
nodes = Seq(Module(modA), Module(modB)),
nodes = Seq(Module(modA), Module(modB), Module(modC)),
edges = Seq(
modA -> modA,
modA -> modB,
modC -> modA,
)
)
test("TreeView should detect cycles and truncate") {
val json = TreeView.processSubtree(graph, Module(modA))
val (rootText, children) = parseTree(json)
assert(rootText == modA.idString)
val childrenText = children.map(parseTree).map(_._1)
val expected = List(s"${modA.idString} (cycle)", modB.idString)
assert(childrenText == expected)
"createJson" should "convert ModuleGraph into JSON correctly" in {
val expected =
"[{\"text\":\"orgC:nameC:3.0\",\"children\":[{\"text\":\"orgA:nameA:1.0\",\"children\":[{\"text\":\"orgA:nameA:1.0 (cycle)\",\"children\":[]},{\"text\":\"orgB:nameB:2.0\",\"children\":[]}]}]}]"
Predef.assert(
createJson(graph) == expected,
s"Expected $expected, but got ${createJson(graph)}"
)
}
@nowarn("cat=unchecked")
def parseTree(json: JSONObject): (String, List[JSONObject]) = {
(json.obj.get("text"), json.obj.get("children")) match {
case (Some(text: String), Some(JSONArray(children: List[JSONObject])))
if children.forall(_.isInstanceOf[JSONObject]) =>
text -> children
case _ =>
fail("a string field 'text' and an array of objects in 'children' field were expected!")
}
"processSubtree" should "detect cycles and truncate" in {
val expected = ModuleModel(
"orgC:nameC:3.0",
Vector(
ModuleModel(
"orgA:nameA:1.0",
Vector(
ModuleModel("orgA:nameA:1.0 (cycle)", Vector()),
ModuleModel("orgB:nameB:2.0", Vector())
)
)
)
)
assert(TreeView.processSubtree(graph, Module(modC), Set()) == expected)
}
}

View File

@ -11,3 +11,4 @@ addSbtPlugin("com.lightbend" % "sbt-whitesource" % "0.1.14")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1")
addSbtPlugin("com.swoval" % "sbt-java-format" % "0.3.1")
addDependencyTreePlugin

View File

@ -0,0 +1,36 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.graph
final class ModuleModel private (
val text: String,
val children: Vector[sbt.internal.graph.ModuleModel]) extends Serializable {
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: ModuleModel => (this.text == x.text) && (this.children == x.children)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.graph.ModuleModel".##) + text.##) + children.##)
}
override def toString: String = {
"ModuleModel(" + text + ", " + children + ")"
}
private[this] def copy(text: String = text, children: Vector[sbt.internal.graph.ModuleModel] = children): ModuleModel = {
new ModuleModel(text, children)
}
def withText(text: String): ModuleModel = {
copy(text = text)
}
def withChildren(children: Vector[sbt.internal.graph.ModuleModel]): ModuleModel = {
copy(children = children)
}
}
object ModuleModel {
def apply(text: String, children: Vector[sbt.internal.graph.ModuleModel]): ModuleModel = new ModuleModel(text, children)
}

View File

@ -0,0 +1,9 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.graph.codec
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.internal.graph.codec.ModuleModelFormats
object JsonProtocol extends JsonProtocol

View File

@ -0,0 +1,29 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.graph.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait ModuleModelFormats { self: sbt.internal.graph.codec.ModuleModelFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val ModuleModelFormat: JsonFormat[sbt.internal.graph.ModuleModel] = new JsonFormat[sbt.internal.graph.ModuleModel] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.graph.ModuleModel = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val text = unbuilder.readField[String]("text")
val children = unbuilder.readField[Vector[sbt.internal.graph.ModuleModel]]("children")
unbuilder.endObject()
sbt.internal.graph.ModuleModel(text, children)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.graph.ModuleModel, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("text", obj.text)
builder.addField("children", obj.children)
builder.endObject()
}
}
}

View File

@ -0,0 +1,9 @@
package sbt.internal.graph
@target(Scala)
@codecPackage("sbt.internal.graph.codec")
@fullCodec("JsonProtocol")
type ModuleModel {
text: String!
children: [sbt.internal.graph.ModuleModel]
}