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

View File

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