Don't hardcode existing relations in TextAnalysisFormat

The previous implementation of TextAnalysisFormat contained the list
of all the existing relations that sbt knew of, and used this
information to write to and read from the disk the persisted analyses.

In this knew implementation, TextAnalysisFormat gets from the
Relations object what are the existing relations, and then persists
them to disk.

The previous situation was not optimal since it meant that, in order
to add a new dependency kind, one had to modify both the Relations
and TextAnalysisFormat.

Using this new implementation, no change to TextAnalysisFormat is
required whenever a new dependency kind is added.
This commit is contained in:
Martin Duhem 2014-09-03 23:35:34 +02:00
parent efa71548d1
commit 82152b8905
2 changed files with 105 additions and 77 deletions

View File

@ -175,9 +175,67 @@ trait Relations {
* Relation between source files and _unqualified_ term and type names used in given source file.
*/
private[inc] def names: Relation[File, String]
/**
* Lists of all the pairs (header, relation) that sbt knows of.
* Used by TextAnalysisFormat to persist relations.
* This cannot be stored as a Map because the order is important.
*/
private[inc] def allRelations: List[(String, Relation[File, _])]
}
object Relations {
/**
* Represents all the relations that sbt knows of along with a way to recreate each
* of their elements from their string representation.
*/
private[inc] val existingRelations = {
val string2File: String => File = new File(_)
List(
("products", string2File),
("binary dependencies", string2File),
("direct source dependencies", string2File),
("direct external dependencies", identity[String] _),
("public inherited source dependencies", string2File),
("public inherited external dependencies", identity[String] _),
("member reference internal dependencies", string2File),
("member reference external dependencies", identity[String] _),
("inheritance internal dependencies", string2File),
("inheritance external dependencies", identity[String] _),
("class names", identity[String] _),
("used names", identity[String] _))
}
/**
* Reconstructs a Relations from a list of Relation
* The order in which the relations are read matters and is defined by `existingRelations`.
*/
def construct(nameHashing: Boolean, relations: List[Relation[_, _]]) =
relations match {
case p :: bin :: di :: de :: pii :: pie :: mri :: mre :: ii :: ie :: cn :: un :: Nil =>
val srcProd = p.asInstanceOf[Relation[File, File]]
val binaryDep = bin.asInstanceOf[Relation[File, File]]
val directSrcDeps = makeSource(di.asInstanceOf[Relation[File, File]], de.asInstanceOf[Relation[File, String]])
val publicInheritedSrcDeps = makeSource(pii.asInstanceOf[Relation[File, File]], pie.asInstanceOf[Relation[File, String]])
val memberRefSrcDeps = makeSourceDependencies(mri.asInstanceOf[Relation[File, File]], mre.asInstanceOf[Relation[File, String]])
val inheritanceSrcDeps = makeSourceDependencies(ii.asInstanceOf[Relation[File, File]], ie.asInstanceOf[Relation[File, String]])
val classes = cn.asInstanceOf[Relation[File, String]]
val names = un.asInstanceOf[Relation[File, String]]
// we don't check for emptiness of publicInherited/inheritance relations because
// we assume that invariant that says they are subsets of direct/memberRef holds
assert(nameHashing || (memberRefSrcDeps == emptySourceDependencies), "When name hashing is disabled the `memberRef` relation should be empty.")
assert(!nameHashing || (directSrcDeps == emptySource), "When name hashing is enabled the `direct` relation should be empty.")
if (nameHashing)
Relations.make(srcProd, binaryDep, memberRefSrcDeps, inheritanceSrcDeps, classes, names)
else {
assert(names.all.isEmpty, s"When `nameHashing` is disabled `names` relation should be empty: $names")
Relations.make(srcProd, binaryDep, directSrcDeps, publicInheritedSrcDeps, classes)
}
case _ => throw new java.io.IOException(s"Expected to read ${existingRelations.length} relations but read ${relations.length}.")
}
/** Tracks internal and external source dependencies for a specific dependency type, such as direct or inherited.*/
final class Source private[sbt] (val internal: Relation[File, File], val external: Relation[File, String]) {
def addInternal(source: File, dependsOn: Iterable[File]): Source = new Source(internal + (source, dependsOn), external)
@ -403,6 +461,23 @@ private class MRelationsDefaultImpl(srcProd: Relation[File, File], binaryDep: Re
case _ => false
}
def allRelations = {
val rels = List(
srcProd,
binaryDep,
direct.internal,
direct.external,
publicInherited.internal,
publicInherited.external,
Relations.emptySourceDependencies.internal, // Default implementation doesn't provide memberRef source deps
Relations.emptySourceDependencies.external, // Default implementation doesn't provide memberRef source deps
Relations.emptySourceDependencies.internal, // Default implementation doesn't provide inheritance source deps
Relations.emptySourceDependencies.external, // Default implementation doesn't provide inheritance source deps
classes,
Relation.empty[File, String]) // Default implementation doesn't provide used names relation
Relations.existingRelations map (_._1) zip rels
}
override def hashCode = (srcProd :: binaryDep :: direct :: publicInherited :: classes :: Nil).hashCode
override def toString = (
@ -490,6 +565,23 @@ private class MRelationsNameHashing(srcProd: Relation[File, File], binaryDep: Re
case _ => false
}
def allRelations = {
val rels = List(
srcProd,
binaryDep,
Relations.emptySource.internal, // NameHashing doesn't provide direct dependencies
Relations.emptySource.external, // NameHashing doesn't provide direct dependencies
Relations.emptySource.internal, // NameHashing doesn't provide public inherited dependencies
Relations.emptySource.external, // NameHashing doesn't provide public inherited dependencies
memberRef.internal,
memberRef.external,
inheritance.internal,
inheritance.external,
classes,
names)
Relations.existingRelations map (_._1) zip rels
}
override def hashCode = (srcProd :: binaryDep :: memberRef :: inheritance :: classes :: Nil).hashCode
override def toString = (

View File

@ -122,7 +122,13 @@ object TextAnalysisFormat {
}
def write(out: Writer, relations: Relations) {
def writeRelation[T](header: String, rel: Relation[File, T])(implicit ord: Ordering[T]) {
// This ordering is used to persist all values in order. Since all values will be
// persisted using their string representation, it makes sense to sort them using
// their string representation.
val toStringOrd = new Ordering[Any] {
def compare(a: Any, b: Any) = a.toString compare b.toString
}
def writeRelation[T](header: String, rel: Relation[File, T]) {
writeHeader(out, header)
writeSize(out, rel.size)
// We sort for ease of debugging and for more efficient reconstruction when reading.
@ -131,38 +137,15 @@ object TextAnalysisFormat {
rel.forwardMap.toSeq.sortBy(_._1).foreach {
case (k, vs) =>
val kStr = k.toString
vs.toSeq.sorted foreach { v =>
vs.toSeq.sorted(toStringOrd) foreach { v =>
out.write(kStr); out.write(" -> "); out.write(v.toString); out.write("\n")
}
}
}
val nameHashing = relations.nameHashing
writeRelation(Headers.srcProd, relations.srcProd)
writeRelation(Headers.binaryDep, relations.binaryDep)
val direct = if (nameHashing) Relations.emptySource else relations.direct
val publicInherited = if (nameHashing)
Relations.emptySource else relations.publicInherited
val memberRef = if (nameHashing)
relations.memberRef else Relations.emptySourceDependencies
val inheritance = if (nameHashing)
relations.inheritance else Relations.emptySourceDependencies
val names = if (nameHashing) relations.names else Relation.empty[File, String]
writeRelation(Headers.directSrcDep, direct.internal)
writeRelation(Headers.directExternalDep, direct.external)
writeRelation(Headers.internalSrcDepPI, publicInherited.internal)
writeRelation(Headers.externalDepPI, publicInherited.external)
writeRelation(Headers.memberRefInternalDep, memberRef.internal)
writeRelation(Headers.memberRefExternalDep, memberRef.external)
writeRelation(Headers.inheritanceInternalDep, inheritance.internal)
writeRelation(Headers.inheritanceExternalDep, inheritance.external)
writeRelation(Headers.classes, relations.classes)
writeRelation(Headers.usedNames, names)
relations.allRelations.foreach {
case (header, rel) => writeRelation(header, rel)
}
}
def read(in: BufferedReader, nameHashing: Boolean): Relations = {
@ -186,56 +169,9 @@ object TextAnalysisFormat {
Relation.reconstruct(forward.toMap)
}
def readFileRelation(expectedHeader: String) = readRelation(expectedHeader, { new File(_) })
def readStringRelation(expectedHeader: String) = readRelation(expectedHeader, identity[String])
val relations = Relations.existingRelations map { case (header, fun) => readRelation(header, fun) }
val srcProd = readFileRelation(Headers.srcProd)
val binaryDep = readFileRelation(Headers.binaryDep)
import sbt.inc.Relations.{
Source,
SourceDependencies,
makeSourceDependencies,
emptySource,
makeSource,
emptySourceDependencies
}
val directSrcDeps: Source = {
val internalSrcDep = readFileRelation(Headers.directSrcDep)
val externalDep = readStringRelation(Headers.directExternalDep)
makeSource(internalSrcDep, externalDep)
}
val publicInheritedSrcDeps: Source = {
val internalSrcDepPI = readFileRelation(Headers.internalSrcDepPI)
val externalDepPI = readStringRelation(Headers.externalDepPI)
makeSource(internalSrcDepPI, externalDepPI)
}
val memberRefSrcDeps: SourceDependencies = {
val internalMemberRefDep = readFileRelation(Headers.memberRefInternalDep)
val externalMemberRefDep = readStringRelation(Headers.memberRefExternalDep)
makeSourceDependencies(internalMemberRefDep, externalMemberRefDep)
}
val inheritanceSrcDeps: SourceDependencies = {
val internalInheritanceDep = readFileRelation(Headers.inheritanceInternalDep)
val externalInheritanceDep = readStringRelation(Headers.inheritanceExternalDep)
makeSourceDependencies(internalInheritanceDep, externalInheritanceDep)
}
// we don't check for emptiness of publicInherited/inheritance relations because
// we assume that invariant that says they are subsets of direct/memberRef holds
assert(nameHashing || (memberRefSrcDeps == emptySourceDependencies),
"When name hashing is disabled the `memberRef` relation should be empty.")
assert(!nameHashing || (directSrcDeps == emptySource),
"When name hashing is enabled the `direct` relation should be empty.")
val classes = readStringRelation(Headers.classes)
val names = readStringRelation(Headers.usedNames)
if (nameHashing)
Relations.make(srcProd, binaryDep, memberRefSrcDeps, inheritanceSrcDeps, classes, names)
else {
assert(names.all.isEmpty, "When `nameHashing` is disabled `names` relation " +
s"should be empty: $names")
Relations.make(srcProd, binaryDep, directSrcDeps, publicInheritedSrcDeps, classes)
}
Relations.construct(nameHashing, relations)
}
}